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

JuliaLang / julia / #38049

23 Apr 2025 06:42AM UTC coverage: 25.767% (+2.5%) from 23.227%
#38049

push

local

web-flow
🤖 [master] Bump the LinearAlgebra stdlib from 1ce8426 to 07725da (#58180)

Stdlib: LinearAlgebra
URL: https://github.com/JuliaLang/LinearAlgebra.jl.git
Stdlib branch: master
Julia branch: master
Old commit: 1ce8426
New commit: 07725da
Julia version: 1.13.0-DEV
LinearAlgebra version: 1.12.0(Does not match)
Bump invoked by: @jishnub
Powered by:
[BumpStdlibs.jl](https://github.com/JuliaLang/BumpStdlibs.jl)

Diff:
https://github.com/JuliaLang/LinearAlgebra.jl/compare/1ce842652...07725da80

```
$ git log --oneline 1ce8426..07725da
07725da Branch on `Bool` `alpha` in scaling `mul!` (#1286)
61e444d Fix exponentiation with immutable matrix (#1289)
77475c1 Fix `lmul!`/`rmul!` for 0-sized matrices (#1290)
c3d35c0 Test for `versioninfo` with `ENV` variable (#1279)
830ea2f Fit broken matmul test (#1288)
222f7f2 Propagate inbounds in diagzero (#1285)
e65e75c Clean up `herk_wrapper!` and add 5-arg tests (#1254)
ece1962 `versioninfo`: simplify, improve type stability (#1274)
3e525a8 Speicalize `copy!` for triangular, and use `copy!` in `ldiv` (#1263)
763f19f Forward scaling `lmul!`/`rmul!` for `Tridiagonal` to bands (#1284)
2d27d1c Explicit loop in converting `Bidiagonal`/`Tridiagonal` to `Matrix` (#1283)
0671a7b Simplify small Bidiagonal-AbstractVecOrMat multiplication (#1278)
a8fd121 Test for empty `Symmetric` and `BlasFlag.NONE` (#1280)
6e2de14 Remove bounds-checking in `Bidiagonal` `rdiv!` (#1281)
4a8fc62 Test for 5-arg `mul!`
c437beb Test for empty `Symmetric` and `BlasFlag.NONE`
7a891e9 Test for `versioninfo` with `ENV` variable
84fd21b Test for `versioninfo` (#1276)
243efdb `AbstractQ` bugfix: define `Base.IteratorSize` (#1277)
6073f28 Ease inference in `Bidiagonal` divmul tests (#1273)
30cb5d6 Avoid copy in empty bidiag `ldiv!` (#1275)
5d3ef46 Skip symmetry check in converting Symmetric to SymTridiagonal (#1269)
ae5385b Specialize `isreal` for banded matrices (#1271)
0a253be Explicit loop in `_isz... (continued)

12876 of 49971 relevant lines covered (25.77%)

1074244.94 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

45
export
46
    AbstractREPL,
47
    BasicREPL,
48
    LineEditREPL,
49
    StreamREPL
50

51
public TerminalMenus
52

53
import Base:
54
    AbstractDisplay,
55
    display,
56
    show,
57
    AnyDict,
58
    ==
59

60
_displaysize(io::IO) = displaysize(io)::Tuple{Int,Int}
×
61

62
include("Terminals.jl")
63
using .Terminals
64

65
abstract type AbstractREPL end
66

67
include("options.jl")
68

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

89
include("SyntaxUtil.jl")
90
include("REPLCompletions.jl")
91
using .REPLCompletions
92

93
include("TerminalMenus/TerminalMenus.jl")
94
include("docview.jl")
95

96
include("Pkg_beforeload.jl")
97

98
@nospecialize # use only declared type signatures
99

100
answer_color(::AbstractREPL) = ""
×
101

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

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

119
    REPLBackend(repl_channel, response_channel, in_eval, ast_transforms=copy(repl_ast_transforms)) =
×
120
        new(repl_channel, response_channel, in_eval, ast_transforms)
121
end
122
REPLBackend() = REPLBackend(Channel(1), Channel(1), false)
×
123

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

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

140
"""
141
    softscope(ex)
142

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

164
# Temporary alias until Documenter updates
165
const softscope! = softscope
166

167
function print_qualified_access_warning(mod::Module, owner::Module, name::Symbol)
×
168
    @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
×
169
end
170

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

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

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

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

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

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

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

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

267
    return nothing
×
268
end
269

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

280
function warn_on_non_owning_accesses(current_mod, ast)
×
281
    warnings = collect_qualified_access_warnings(current_mod, ast)
×
282
    for (; outer_mod, mod, owner, name_being_accessed) in warnings
×
283
        print_qualified_access_warning(mod, owner, name_being_accessed)
×
284
    end
×
285
    return ast
×
286
end
287
warn_on_non_owning_accesses(ast) = warn_on_non_owning_accesses(Base.active_module(), ast)
×
288

289
const repl_ast_transforms = Any[softscope, warn_on_non_owning_accesses] # defaults for new REPL backends
290

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

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

305
function toplevel_eval_with_hooks(mod::Module, @nospecialize(ast), toplevel_file=Ref{Ptr{UInt8}}(Base.unsafe_convert(Ptr{UInt8}, :REPL)), toplevel_line=Ref{Cint}(1))
×
306
    if !isexpr(ast, :toplevel)
×
307
        ast = invokelatest(__repl_entry_lower_with_loc, mod, ast, toplevel_file, toplevel_line)
×
308
        check_for_missing_packages_and_run_hooks(ast)
×
309
        return invokelatest(__repl_entry_eval_expanded_with_loc, mod, ast, toplevel_file, toplevel_line)
×
310
    end
311
    local value=nothing
×
312
    for i = 1:length(ast.args)
×
313
        value = toplevel_eval_with_hooks(mod, ast.args[i], toplevel_file, toplevel_line)
×
314
    end
×
315
    return value
×
316
end
317

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

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

361
function _modules_to_be_loaded!(ast::Expr, mods::Vector{Symbol})
×
362
    ast.head === :quote && return mods # don't search if it's not going to be run during this eval
×
363
    if ast.head === :using || ast.head === :import
×
364
        for arg in ast.args
×
365
            arg = arg::Expr
×
366
            arg1 = first(arg.args)
×
367
            if arg1 isa Symbol # i.e. `Foo`
×
368
                if arg1 != :. # don't include local import `import .Foo`
×
369
                    push!(mods, arg1)
×
370
                end
371
            else # i.e. `Foo: bar`
372
                sym = first((arg1::Expr).args)::Symbol
×
373
                if sym != :. # don't include local import `import .Foo: a`
×
374
                    push!(mods, sym)
×
375
                end
376
            end
377
        end
×
378
    end
379
    if ast.head !== :thunk
×
380
        for arg in ast.args
×
381
            if isexpr(arg, (:block, :if, :using, :import))
×
382
                _modules_to_be_loaded!(arg, mods)
×
383
            end
384
        end
×
385
    else
386
        code = ast.args[1]
×
387
        for arg in code.code
×
388
            isa(arg, Expr) || continue
×
389
            _modules_to_be_loaded!(arg, mods)
×
390
        end
×
391
    end
392
end
393

394
function modules_to_be_loaded(ast::Expr, mods::Vector{Symbol} = Symbol[])
×
395
    _modules_to_be_loaded!(ast, mods)
×
396
    filter!(mod::Symbol -> !in(mod, (:Base, :Main, :Core)), mods) # Exclude special non-package modules
×
397
    return unique(mods)
×
398
end
399

400
"""
401
    start_repl_backend(repl_channel::Channel, response_channel::Channel)
402

403
    Starts loop for REPL backend
404
    Returns a REPLBackend with backend_task assigned
405

406
    Deprecated since sync / async behavior cannot be selected
407
"""
408
function start_repl_backend(repl_channel::Channel{Any}, response_channel::Channel{Any}
×
409
                            ; get_module::Function = ()->Main)
410
    # Maintain legacy behavior of asynchronous backend
411
    backend = REPLBackend(repl_channel, response_channel, false)
×
412
    # Assignment will be made twice, but will be immediately available
413
    backend.backend_task = @async start_repl_backend(backend; get_module)
×
414
    return backend
×
415
end
416

417
"""
418
    start_repl_backend(backend::REPLBackend)
419

420
    Call directly to run backend loop on current Task.
421
    Use @async for run backend on new Task.
422

423
    Does not return backend until loop is finished.
424
"""
425
function start_repl_backend(backend::REPLBackend,  @nospecialize(consumer = x -> nothing); get_module::Function = ()->Main)
×
426
    backend.backend_task = Base.current_task()
×
427
    consumer(backend)
×
428
    repl_backend_loop(backend, get_module)
×
429
    return backend
×
430
end
431

432
function repl_backend_loop(backend::REPLBackend, get_module::Function)
×
433
    # include looks at this to determine the relative include path
434
    # nothing means cwd
435
    while true
×
436
        tls = task_local_storage()
×
437
        tls[:SOURCE_PATH] = nothing
×
438
        ast_or_func, show_value = take!(backend.repl_channel)
×
439
        if show_value == -1
×
440
            # exit flag
441
            break
×
442
        end
443
        if show_value == 2 # 2 indicates a function to be called
×
444
            f = ast_or_func
×
445
            try
×
446
                ret = f()
×
447
                put!(backend.response_channel, Pair{Any, Bool}(ret, false))
×
448
            catch err
449
                put!(backend.response_channel, Pair{Any, Bool}(err, true))
×
450
            end
451
        else
452
            ast = ast_or_func
×
453
            eval_user_input(ast, backend, get_module())
×
454
        end
455
    end
×
456
    return nothing
×
457
end
458

459
SHOW_MAXIMUM_BYTES::Int = 1_048_576
460

461
# Limit printing during REPL display
462
mutable struct LimitIO{IO_t <: IO} <: IO
463
    io::IO_t
464
    maxbytes::Int
465
    n::Int # max bytes to write
466
end
467
LimitIO(io::IO, maxbytes) = LimitIO(io, maxbytes, 0)
×
468

469
struct LimitIOException <: Exception
470
    maxbytes::Int
471
end
472

473
function Base.showerror(io::IO, e::LimitIOException)
×
474
    print(io, "$LimitIOException: aborted printing after attempting to print more than $(Base.format_bytes(e.maxbytes)) within a `LimitIO`.")
×
475
end
476

477
function Base.write(io::LimitIO, v::UInt8)
×
478
    io.n > io.maxbytes && throw(LimitIOException(io.maxbytes))
×
479
    n_bytes = write(io.io, v)
×
480
    io.n += n_bytes
×
481
    return n_bytes
×
482
end
483

484
# Semantically, we only need to override `Base.write`, but we also
485
# override `unsafe_write` for performance.
486
function Base.unsafe_write(limiter::LimitIO, p::Ptr{UInt8}, nb::UInt)
×
487
    # already exceeded? throw
488
    limiter.n > limiter.maxbytes && throw(LimitIOException(limiter.maxbytes))
×
489
    remaining = limiter.maxbytes - limiter.n # >= 0
×
490

491
    # Not enough bytes left; we will print up to the limit, then throw
492
    if remaining < nb
×
493
        if remaining > 0
×
494
            Base.unsafe_write(limiter.io, p, remaining)
×
495
        end
496
        throw(LimitIOException(limiter.maxbytes))
×
497
    end
498

499
    # We won't hit the limit so we'll write the full `nb` bytes
500
    bytes_written = Base.unsafe_write(limiter.io, p, nb)::Union{Int,UInt}
×
501
    limiter.n += bytes_written
×
502
    return bytes_written
×
503
end
504

505
struct REPLDisplay{Repl<:AbstractREPL} <: AbstractDisplay
506
    repl::Repl
507
end
508

509
function show_limited(io::IO, mime::MIME, x)
×
510
    try
×
511
        # We wrap in a LimitIO to limit the amount of printing.
512
        # We unpack `IOContext`s, since we will pass the properties on the outside.
513
        inner = io isa IOContext ? io.io : io
×
514
        wrapped_limiter = IOContext(LimitIO(inner, SHOW_MAXIMUM_BYTES), io)
×
515
        # `show_repl` to allow the hook with special syntax highlighting
516
        show_repl(wrapped_limiter, mime, x)
×
517
    catch e
518
        e isa LimitIOException || rethrow()
×
519
        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)
×
520
    end
521
end
522

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

545
display(d::REPLDisplay, x) = display(d, MIME("text/plain"), x)
×
546

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

549
show_repl(io::IO, ::MIME"text/plain", ex::Expr) =
×
550
    print(io, JuliaSyntaxHighlighting.highlight(
551
        sprint(show, ex, context=IOContext(io, :color => false))))
552

553
function print_response(repl::AbstractREPL, response, show_value::Bool, have_color::Bool)
×
554
    repl.waserror = response[2]
×
555
    with_repl_linfo(repl) do io
×
556
        io = IOContext(io, :module => Base.active_module(repl)::Module)
×
557
        print_response(io, response, backend(repl), show_value, have_color, specialdisplay(repl))
×
558
    end
559
    return nothing
×
560
end
561

562
function repl_display_error(errio::IO, @nospecialize errval)
×
563
    # this will be set to true if types in the stacktrace are truncated
564
    limitflag = Ref(false)
×
565
    errio = IOContext(errio, :stacktrace_types_limited => limitflag)
×
566
    Base.invokelatest(Base.display_error, errio, errval)
×
567
    if limitflag[]
×
568
        print(errio, "Some type information was truncated. Use `show(err)` to see complete types.")
×
569
        println(errio)
×
570
    end
571
    return nothing
×
572
end
573

574
function print_response(errio::IO, response, backend::Union{REPLBackendRef,Nothing}, show_value::Bool, have_color::Bool, specialdisplay::Union{AbstractDisplay,Nothing}=nothing)
×
575
    Base.sigatomic_begin()
×
576
    val, iserr = response
×
577
    while true
×
578
        try
×
579
            Base.sigatomic_end()
×
580
            if iserr
×
581
                val = Base.scrub_repl_backtrace(val)
×
582
                Base.istrivialerror(val) || setglobal!(Base.MainInclude, :err, val)
×
583
                repl_display_error(errio, val)
×
584
            else
585
                if val !== nothing && show_value
×
586
                    val2, iserr = if specialdisplay === nothing
×
587
                        # display calls may require being run on the main thread
588
                        eval_with_backend(backend) do
×
589
                            Base.invokelatest(display, val)
×
590
                        end
591
                    else
592
                        eval_with_backend(backend) do
×
593
                            Base.invokelatest(display, specialdisplay, val)
×
594
                        end
595
                    end
596
                    if iserr
×
597
                        println(errio, "Error showing value of type ", typeof(val), ":")
×
598
                        throw(val2)
×
599
                    end
600
                end
601
            end
602
            break
×
603
        catch ex
604
            if iserr
×
605
                println(errio) # an error during printing is likely to leave us mid-line
×
606
                println(errio, "SYSTEM (REPL): showing an error caused an error")
×
607
                try
×
608
                    excs = Base.scrub_repl_backtrace(current_exceptions())
×
609
                    setglobal!(Base.MainInclude, :err, excs)
×
610
                    repl_display_error(errio, excs)
×
611
                catch e
612
                    # at this point, only print the name of the type as a Symbol to
613
                    # minimize the possibility of further errors.
614
                    println(errio)
×
615
                    println(errio, "SYSTEM (REPL): caught exception of type ", typeof(e).name.name,
×
616
                            " while trying to handle a nested exception; giving up")
617
                end
618
                break
×
619
            end
620
            val = current_exceptions()
×
621
            iserr = true
×
622
        end
623
    end
×
624
    Base.sigatomic_end()
×
625
    nothing
×
626
end
627

628

629

630
"""
631
    run_repl(repl::AbstractREPL)
632
    run_repl(repl, consumer = backend->nothing; backend_on_current_task = true)
633

634
    Main function to start the REPL
635

636
    consumer is an optional function that takes a REPLBackend as an argument
637
"""
638
function run_repl(repl::AbstractREPL, @nospecialize(consumer = x -> nothing); backend_on_current_task::Bool = true, backend = REPLBackend())
×
639
    backend_ref = REPLBackendRef(backend)
×
640
    cleanup = @task try
×
641
            destroy(backend_ref, t)
×
642
        catch e
643
            Core.print(Core.stderr, "\nINTERNAL ERROR: ")
×
644
            Core.println(Core.stderr, e)
×
645
            Core.println(Core.stderr, catch_backtrace())
×
646
        end
647
    get_module = () -> Base.active_module(repl)
×
648
    if backend_on_current_task
×
649
        t = @async run_frontend(repl, backend_ref)
×
650
        errormonitor(t)
×
651
        Base._wait2(t, cleanup)
×
652
        start_repl_backend(backend, consumer; get_module)
×
653
    else
654
        t = @async start_repl_backend(backend, consumer; get_module)
×
655
        errormonitor(t)
×
656
        Base._wait2(t, cleanup)
×
657
        run_frontend(repl, backend_ref)
×
658
    end
659
    return backend
×
660
end
661

662
## BasicREPL ##
663

664
mutable struct BasicREPL <: AbstractREPL
665
    terminal::TextTerminal
666
    waserror::Bool
667
    frontend_task::Task
668
    BasicREPL(t) = new(t, false)
×
669
end
670

671
outstream(r::BasicREPL) = r.terminal
×
672
hascolor(r::BasicREPL) = hascolor(r.terminal)
×
673

674
function run_frontend(repl::BasicREPL, backend::REPLBackendRef)
×
675
    repl.frontend_task = current_task()
×
676
    d = REPLDisplay(repl)
×
677
    dopushdisplay = !in(d,Base.Multimedia.displays)
×
678
    dopushdisplay && pushdisplay(d)
×
679
    hit_eof = false
×
680
    while true
×
681
        Base.reseteof(repl.terminal)
×
682
        write(repl.terminal, JULIA_PROMPT)
×
683
        line = ""
×
684
        ast = nothing
×
685
        interrupted = false
×
686
        while true
×
687
            try
×
688
                line *= readline(repl.terminal, keep=true)
×
689
            catch e
690
                if isa(e,InterruptException)
×
691
                    try # raise the debugger if present
×
692
                        ccall(:jl_raise_debugger, Int, ())
×
693
                    catch
×
694
                    end
695
                    line = ""
×
696
                    interrupted = true
×
697
                    break
×
698
                elseif isa(e,EOFError)
×
699
                    hit_eof = true
×
700
                    break
×
701
                else
702
                    rethrow()
×
703
                end
704
            end
705
            ast = Base.parse_input_line(line)
×
706
            (isa(ast,Expr) && ast.head === :incomplete) || break
×
707
        end
×
708
        if !isempty(line)
×
709
            response = eval_with_backend(ast, backend)
×
710
            print_response(repl, response, !ends_with_semicolon(line), false)
×
711
        end
712
        write(repl.terminal, '\n')
×
713
        ((!interrupted && isempty(line)) || hit_eof) && break
×
714
    end
×
715
    # terminate backend
716
    put!(backend.repl_channel, (nothing, -1))
×
717
    dopushdisplay && popdisplay(d)
×
718
    nothing
×
719
end
720

721
## LineEditREPL ##
722

723
mutable struct LineEditREPL <: AbstractREPL
724
    t::TextTerminal
725
    hascolor::Bool
726
    prompt_color::String
727
    input_color::String
728
    answer_color::String
729
    shell_color::String
730
    help_color::String
731
    pkg_color::String
732
    history_file::Bool
733
    in_shell::Bool
734
    in_help::Bool
735
    envcolors::Bool
736
    waserror::Bool
737
    specialdisplay::Union{Nothing,AbstractDisplay}
738
    options::Options
739
    mistate::Union{MIState,Nothing}
740
    last_shown_line_infos::Vector{Tuple{String,Int}}
741
    interface::ModalInterface
742
    backendref::REPLBackendRef
743
    frontend_task::Task
744
    function LineEditREPL(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,pkg_color,history_file,in_shell,in_help,envcolors)
×
745
        opts = Options()
×
746
        opts.hascolor = hascolor
×
747
        if !hascolor
×
748
            opts.beep_colors = [""]
×
749
        end
750
        new(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,pkg_color,history_file,in_shell,
×
751
            in_help,envcolors,false,nothing, opts, nothing, Tuple{String,Int}[])
752
    end
753
end
754
outstream(r::LineEditREPL) = (t = r.t; t isa TTYTerminal ? t.out_stream : t)
×
755
specialdisplay(r::LineEditREPL) = r.specialdisplay
×
756
specialdisplay(r::AbstractREPL) = nothing
×
757
terminal(r::LineEditREPL) = r.t
×
758
hascolor(r::LineEditREPL) = r.hascolor
×
759

760
LineEditREPL(t::TextTerminal, hascolor::Bool, envcolors::Bool=false) =
×
761
    LineEditREPL(t, hascolor,
762
        hascolor ? Base.text_colors[:green] : "",
763
        hascolor ? Base.input_color() : "",
764
        hascolor ? Base.answer_color() : "",
765
        hascolor ? Base.text_colors[:red] : "",
766
        hascolor ? Base.text_colors[:yellow] : "",
767
        hascolor ? Base.text_colors[:blue] : "",
768
        false, false, false, envcolors
769
    )
770

771
mutable struct REPLCompletionProvider <: CompletionProvider
772
    modifiers::LineEdit.Modifiers
773
end
774
REPLCompletionProvider() = REPLCompletionProvider(LineEdit.Modifiers())
×
775

776
mutable struct ShellCompletionProvider <: CompletionProvider end
777
struct LatexCompletions <: CompletionProvider end
778

779
Base.active_module((; mistate)::LineEditREPL) = mistate === nothing ? Main : mistate.active_module
×
780
Base.active_module(::AbstractREPL) = Main
×
781
Base.active_module(d::REPLDisplay) = Base.active_module(d.repl)
×
782

783
setmodifiers!(c::CompletionProvider, m::LineEdit.Modifiers) = nothing
×
784

785
setmodifiers!(c::REPLCompletionProvider, m::LineEdit.Modifiers) = c.modifiers = m
×
786

787
"""
788
    activate(mod::Module=Main)
789

790
Set `mod` as the default contextual module in the REPL,
791
both for evaluating expressions and printing them.
792
"""
793
function activate(mod::Module=Main; interactive_utils::Bool=true)
×
794
    mistate = (Base.active_repl::LineEditREPL).mistate
×
795
    mistate === nothing && return nothing
×
796
    mistate.active_module = mod
×
797
    interactive_utils && Base.load_InteractiveUtils(mod)
×
798
    return nothing
×
799
end
800

801
beforecursor(buf::IOBuffer) = String(buf.data[1:buf.ptr-1])
×
802

803
# Convert inclusive-inclusive 1-based char indexing to inclusive-exclusive byte Region.
804
to_region(s, r) = first(r)-1 => (length(r) > 0 ? nextind(s, last(r))-1 : first(r)-1)
×
805

806
function complete_line(c::REPLCompletionProvider, s::PromptState, mod::Module; hint::Bool=false)
×
807
    full = LineEdit.input_string(s)
×
808
    ret, range, should_complete = completions(full, thisind(full, position(s)), mod, c.modifiers.shift, hint)
×
809
    range = to_region(full, range)
×
810
    c.modifiers = LineEdit.Modifiers()
×
811
    return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
×
812
end
813

814
function complete_line(c::ShellCompletionProvider, s::PromptState; hint::Bool=false)
×
815
    full = LineEdit.input_string(s)
×
816
    ret, range, should_complete = shell_completions(full, thisind(full, position(s)), hint)
×
817
    range = to_region(full, range)
×
818
    return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
×
819
end
820

821
function complete_line(c::LatexCompletions, s; hint::Bool=false)
×
822
    full = LineEdit.input_string(s)::String
×
823
    ret, range, should_complete = bslash_completions(full, thisind(full, position(s)), hint)[2]
×
824
    range = to_region(full, range)
×
825
    return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
×
826
end
827

828
with_repl_linfo(f, repl) = f(outstream(repl))
×
829
function with_repl_linfo(f, repl::LineEditREPL)
×
830
    linfos = Tuple{String,Int}[]
×
831
    io = IOContext(outstream(repl), :last_shown_line_infos => linfos)
×
832
    f(io)
×
833
    if !isempty(linfos)
×
834
        repl.last_shown_line_infos = linfos
×
835
    end
836
    nothing
×
837
end
838

839
mutable struct REPLHistoryProvider <: HistoryProvider
840
    history::Vector{String}
841
    file_path::String
842
    history_file::Union{Nothing,IO}
843
    start_idx::Int
844
    cur_idx::Int
845
    last_idx::Int
846
    last_buffer::IOBuffer
847
    last_mode::Union{Nothing,Prompt}
848
    mode_mapping::Dict{Symbol,Prompt}
849
    modes::Vector{Symbol}
850
end
851
REPLHistoryProvider(mode_mapping::Dict{Symbol}) =
×
852
    REPLHistoryProvider(String[], "", nothing, 0, 0, -1, IOBuffer(),
853
                        nothing, mode_mapping, UInt8[])
854

855
invalid_history_message(path::String) = """
×
856
Invalid history file ($path) format:
857
If you have a history file left over from an older version of Julia,
858
try renaming or deleting it.
859
Invalid character: """
860

861
munged_history_message(path::String) = """
×
862
Invalid history file ($path) format:
863
An editor may have converted tabs to spaces at line """
864

865
function hist_open_file(hp::REPLHistoryProvider)
×
866
    f = open(hp.file_path, read=true, write=true, create=true)
×
867
    hp.history_file = f
×
868
    seekend(f)
×
869
end
870

871
function hist_from_file(hp::REPLHistoryProvider, path::String)
×
872
    getline(lines, i) = i > length(lines) ? "" : lines[i]
×
873
    file_lines = readlines(path)
×
874
    countlines = 0
×
875
    while true
×
876
        # First parse the metadata that starts with '#' in particular the REPL mode
877
        countlines += 1
×
878
        line = getline(file_lines, countlines)
×
879
        mode = :julia
×
880
        isempty(line) && break
×
881
        line[1] != '#' &&
×
882
            error(invalid_history_message(path), repr(line[1]), " at line ", countlines)
883
        while !isempty(line)
×
884
            startswith(line, '#') || break
×
885
            if startswith(line, "# mode: ")
×
886
                mode = Symbol(SubString(line, 9))
×
887
            end
888
            countlines += 1
×
889
            line = getline(file_lines, countlines)
×
890
        end
×
891
        isempty(line) && break
×
892

893
        # Now parse the code for the current REPL mode
894
        line[1] == ' '  &&
×
895
            error(munged_history_message(path), countlines)
896
        line[1] != '\t' &&
×
897
            error(invalid_history_message(path), repr(line[1]), " at line ", countlines)
898
        lines = String[]
×
899
        while !isempty(line)
×
900
            push!(lines, chomp(SubString(line, 2)))
×
901
            next_line = getline(file_lines, countlines+1)
×
902
            isempty(next_line) && break
×
903
            first(next_line) == ' '  && error(munged_history_message(path), countlines)
×
904
            # A line not starting with a tab means we are done with code for this entry
905
            first(next_line) != '\t' && break
×
906
            countlines += 1
×
907
            line = getline(file_lines, countlines)
×
908
        end
×
909
        push!(hp.modes, mode)
×
910
        push!(hp.history, join(lines, '\n'))
×
911
    end
×
912
    hp.start_idx = length(hp.history)
×
913
    return hp
×
914
end
915

916
function add_history(hist::REPLHistoryProvider, s::PromptState)
×
917
    str = rstrip(String(take!(copy(s.input_buffer))))
×
918
    isempty(strip(str)) && return
×
919
    mode = mode_idx(hist, LineEdit.mode(s))
×
920
    !isempty(hist.history) &&
×
921
        isequal(mode, hist.modes[end]) && str == hist.history[end] && return
922
    push!(hist.modes, mode)
×
923
    push!(hist.history, str)
×
924
    hist.history_file === nothing && return
×
925
    entry = """
×
926
    # time: $(Libc.strftime("%Y-%m-%d %H:%M:%S %Z", time()))
927
    # mode: $mode
928
    $(replace(str, r"^"ms => "\t"))
×
929
    """
930
    # TODO: write-lock history file
931
    try
×
932
        seekend(hist.history_file)
×
933
    catch err
934
        (err isa SystemError) || rethrow()
×
935
        # File handle might get stale after a while, especially under network file systems
936
        # If this doesn't fix it (e.g. when file is deleted), we'll end up rethrowing anyway
937
        hist_open_file(hist)
×
938
    end
939
    print(hist.history_file, entry)
×
940
    flush(hist.history_file)
×
941
    nothing
×
942
end
943

944
function history_move(s::Union{LineEdit.MIState,LineEdit.PrefixSearchState}, hist::REPLHistoryProvider, idx::Int, save_idx::Int = hist.cur_idx)
×
945
    max_idx = length(hist.history) + 1
×
946
    @assert 1 <= hist.cur_idx <= max_idx
×
947
    (1 <= idx <= max_idx) || return :none
×
948
    idx != hist.cur_idx || return :none
×
949

950
    # save the current line
951
    if save_idx == max_idx
×
952
        hist.last_mode = LineEdit.mode(s)
×
953
        hist.last_buffer = copy(LineEdit.buffer(s))
×
954
    else
955
        hist.history[save_idx] = LineEdit.input_string(s)
×
956
        hist.modes[save_idx] = mode_idx(hist, LineEdit.mode(s))
×
957
    end
958

959
    # load the saved line
960
    if idx == max_idx
×
961
        last_buffer = hist.last_buffer
×
962
        LineEdit.transition(s, hist.last_mode) do
×
963
            LineEdit.replace_line(s, last_buffer)
×
964
        end
965
        hist.last_mode = nothing
×
966
        hist.last_buffer = IOBuffer()
×
967
    else
968
        if haskey(hist.mode_mapping, hist.modes[idx])
×
969
            LineEdit.transition(s, hist.mode_mapping[hist.modes[idx]]) do
×
970
                LineEdit.replace_line(s, hist.history[idx])
×
971
            end
972
        else
973
            return :skip
×
974
        end
975
    end
976
    hist.cur_idx = idx
×
977

978
    return :ok
×
979
end
980

981
# REPL History can also transitions modes
982
function LineEdit.accept_result_newmode(hist::REPLHistoryProvider)
×
983
    if 1 <= hist.cur_idx <= length(hist.modes)
×
984
        return hist.mode_mapping[hist.modes[hist.cur_idx]]
×
985
    end
986
    return nothing
×
987
end
988

989
function history_prev(s::LineEdit.MIState, hist::REPLHistoryProvider,
×
990
                      num::Int=1, save_idx::Int = hist.cur_idx)
991
    num <= 0 && return history_next(s, hist, -num, save_idx)
×
992
    hist.last_idx = -1
×
993
    m = history_move(s, hist, hist.cur_idx-num, save_idx)
×
994
    if m === :ok
×
995
        LineEdit.move_input_start(s)
×
996
        LineEdit.reset_key_repeats(s) do
×
997
            LineEdit.move_line_end(s)
×
998
        end
999
        return LineEdit.refresh_line(s)
×
1000
    elseif m === :skip
×
1001
        return history_prev(s, hist, num+1, save_idx)
×
1002
    else
1003
        return Terminals.beep(s)
×
1004
    end
1005
end
1006

1007
function history_next(s::LineEdit.MIState, hist::REPLHistoryProvider,
×
1008
                      num::Int=1, save_idx::Int = hist.cur_idx)
1009
    if num == 0
×
1010
        Terminals.beep(s)
×
1011
        return
×
1012
    end
1013
    num < 0 && return history_prev(s, hist, -num, save_idx)
×
1014
    cur_idx = hist.cur_idx
×
1015
    max_idx = length(hist.history) + 1
×
1016
    if cur_idx == max_idx && 0 < hist.last_idx
×
1017
        # issue #6312
1018
        cur_idx = hist.last_idx
×
1019
        hist.last_idx = -1
×
1020
    end
1021
    m = history_move(s, hist, cur_idx+num, save_idx)
×
1022
    if m === :ok
×
1023
        LineEdit.move_input_end(s)
×
1024
        return LineEdit.refresh_line(s)
×
1025
    elseif m === :skip
×
1026
        return history_next(s, hist, num+1, save_idx)
×
1027
    else
1028
        return Terminals.beep(s)
×
1029
    end
1030
end
1031

1032
history_first(s::LineEdit.MIState, hist::REPLHistoryProvider) =
×
1033
    history_prev(s, hist, hist.cur_idx - 1 -
1034
                 (hist.cur_idx > hist.start_idx+1 ? hist.start_idx : 0))
1035

1036
history_last(s::LineEdit.MIState, hist::REPLHistoryProvider) =
×
1037
    history_next(s, hist, length(hist.history) - hist.cur_idx + 1)
1038

1039
function history_move_prefix(s::LineEdit.PrefixSearchState,
×
1040
                             hist::REPLHistoryProvider,
1041
                             prefix::AbstractString,
1042
                             backwards::Bool,
1043
                             cur_idx::Int = hist.cur_idx)
1044
    cur_response = String(take!(copy(LineEdit.buffer(s))))
×
1045
    # when searching forward, start at last_idx
1046
    if !backwards && hist.last_idx > 0
×
1047
        cur_idx = hist.last_idx
×
1048
    end
1049
    hist.last_idx = -1
×
1050
    max_idx = length(hist.history)+1
×
1051
    idxs = backwards ? ((cur_idx-1):-1:1) : ((cur_idx+1):1:max_idx)
×
1052
    for idx in idxs
×
1053
        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)))
×
1054
            m = history_move(s, hist, idx)
×
1055
            if m === :ok
×
1056
                if idx == max_idx
×
1057
                    # on resuming the in-progress edit, leave the cursor where the user last had it
1058
                elseif isempty(prefix)
×
1059
                    # on empty prefix search, move cursor to the end
1060
                    LineEdit.move_input_end(s)
×
1061
                else
1062
                    # otherwise, keep cursor at the prefix position as a visual cue
1063
                    seek(LineEdit.buffer(s), sizeof(prefix))
×
1064
                end
1065
                LineEdit.refresh_line(s)
×
1066
                return :ok
×
1067
            elseif m === :skip
×
1068
                return history_move_prefix(s,hist,prefix,backwards,idx)
×
1069
            end
1070
        end
1071
    end
×
1072
    Terminals.beep(s)
×
1073
    nothing
×
1074
end
1075
history_next_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::AbstractString) =
×
1076
    history_move_prefix(s, hist, prefix, false)
1077
history_prev_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::AbstractString) =
×
1078
    history_move_prefix(s, hist, prefix, true)
1079

1080
function history_search(hist::REPLHistoryProvider, query_buffer::IOBuffer, response_buffer::IOBuffer,
×
1081
                        backwards::Bool=false, skip_current::Bool=false)
1082

1083
    qpos = position(query_buffer)
×
1084
    qpos > 0 || return true
×
1085
    searchdata = beforecursor(query_buffer)
×
1086
    response_str = String(take!(copy(response_buffer)))
×
1087

1088
    # Alright, first try to see if the current match still works
1089
    a = position(response_buffer) + 1 # position is zero-indexed
×
1090
    # FIXME: I'm pretty sure this is broken since it uses an index
1091
    # into the search data to index into the response string
1092
    b = a + sizeof(searchdata)
×
1093
    b = b ≤ ncodeunits(response_str) ? prevind(response_str, b) : b-1
×
1094
    b = min(lastindex(response_str), b) # ensure that b is valid
×
1095

1096
    searchstart = backwards ? b : a
×
1097
    if searchdata == response_str[a:b]
×
1098
        if skip_current
×
1099
            searchstart = backwards ? prevind(response_str, b) : nextind(response_str, a)
×
1100
        else
1101
            return true
×
1102
        end
1103
    end
1104

1105
    # Start searching
1106
    # First the current response buffer
1107
    if 1 <= searchstart <= lastindex(response_str)
×
1108
        match = backwards ? findprev(searchdata, response_str, searchstart) :
×
1109
                            findnext(searchdata, response_str, searchstart)
1110
        if match !== nothing
×
1111
            seek(response_buffer, first(match) - 1)
×
1112
            return true
×
1113
        end
1114
    end
1115

1116
    # Now search all the other buffers
1117
    idxs = backwards ? ((hist.cur_idx-1):-1:1) : ((hist.cur_idx+1):1:length(hist.history))
×
1118
    for idx in idxs
×
1119
        h = hist.history[idx]
×
1120
        match = backwards ? findlast(searchdata, h) : findfirst(searchdata, h)
×
1121
        if match !== nothing && h != response_str && haskey(hist.mode_mapping, hist.modes[idx])
×
1122
            truncate(response_buffer, 0)
×
1123
            write(response_buffer, h)
×
1124
            seek(response_buffer, first(match) - 1)
×
1125
            hist.cur_idx = idx
×
1126
            return true
×
1127
        end
1128
    end
×
1129

1130
    return false
×
1131
end
1132

1133
function history_reset_state(hist::REPLHistoryProvider)
×
1134
    if hist.cur_idx != length(hist.history) + 1
×
1135
        hist.last_idx = hist.cur_idx
×
1136
        hist.cur_idx = length(hist.history) + 1
×
1137
    end
1138
    nothing
×
1139
end
1140
LineEdit.reset_state(hist::REPLHistoryProvider) = history_reset_state(hist)
×
1141

1142
function return_callback(s)
×
1143
    ast = Base.parse_input_line(String(take!(copy(LineEdit.buffer(s)))), depwarn=false)
×
1144
    return !(isa(ast, Expr) && ast.head === :incomplete)
×
1145
end
1146

1147
find_hist_file() = get(ENV, "JULIA_HISTORY",
×
1148
                       !isempty(DEPOT_PATH) ? joinpath(DEPOT_PATH[1], "logs", "repl_history.jl") :
1149
                       error("DEPOT_PATH is empty and ENV[\"JULIA_HISTORY\"] not set."))
1150

1151
backend(r::AbstractREPL) = hasproperty(r, :backendref) ? r.backendref : nothing
×
1152

1153

1154
function eval_with_backend(ast::Expr, backend::REPLBackendRef)
×
1155
    put!(backend.repl_channel, (ast, 1)) # (f, show_value)
×
1156
    return take!(backend.response_channel) # (val, iserr)
×
1157
end
1158
function eval_with_backend(f, backend::REPLBackendRef)
×
1159
    put!(backend.repl_channel, (f, 2)) # (f, show_value) 2 indicates function (rather than ast)
×
1160
    return take!(backend.response_channel) # (val, iserr)
×
1161
end
1162
# if no backend just eval (used by tests)
1163
function eval_with_backend(f, backend::Nothing)
×
1164
    try
×
1165
        ret = f()
×
1166
        return (ret, false) # (val, iserr)
×
1167
    catch err
1168
        return (err, true)
×
1169
    end
1170
end
1171

1172

1173
function respond(f, repl, main; pass_empty::Bool = false, suppress_on_semicolon::Bool = true)
×
1174
    return function do_respond(s::MIState, buf, ok::Bool)
×
1175
        if !ok
×
1176
            return transition(s, :abort)
×
1177
        end
1178
        line = String(take!(buf)::Vector{UInt8})
×
1179
        if !isempty(line) || pass_empty
×
1180
            reset(repl)
×
1181
            local response
×
1182
            try
×
1183
                ast = Base.invokelatest(f, line)
×
1184
                response = eval_with_backend(ast, backend(repl))
×
1185
            catch
1186
                response = Pair{Any, Bool}(current_exceptions(), true)
×
1187
            end
1188
            hide_output = suppress_on_semicolon && ends_with_semicolon(line)
×
1189
            print_response(repl, response, !hide_output, hascolor(repl))
×
1190
        end
1191
        prepare_next(repl)
×
1192
        reset_state(s)
×
1193
        return s.current_mode.sticky ? true : transition(s, main)
×
1194
    end
1195
end
1196

1197
function reset(repl::LineEditREPL)
×
1198
    raw!(repl.t, false)
×
1199
    hascolor(repl) && print(repl.t, Base.text_colors[:normal])
×
1200
    nothing
×
1201
end
1202

1203
function prepare_next(repl::LineEditREPL)
×
1204
    println(terminal(repl))
×
1205
end
1206

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

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

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

1235
function contextual_prompt(repl::LineEditREPL, prompt::Union{String,Function})
×
1236
    function ()
×
1237
        mod = Base.active_module(repl)
×
1238
        prefix = mod == Main ? "" : string('(', mod, ") ")
×
1239
        pr = prompt isa String ? prompt : prompt()
×
1240
        prefix * pr
×
1241
    end
1242
end
1243

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

1251

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

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

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

1283
    ############################### Stage I ################################
1284

1285
    # This will provide completions for REPL and help mode
1286
    replc = REPLCompletionProvider()
×
1287

1288
    # Set up the main Julia prompt
1289
    julia_prompt = Prompt(contextual_prompt(repl, JULIA_PROMPT);
×
1290
        # Copy colors from the prompt object
1291
        prompt_prefix = hascolor ? repl.prompt_color : "",
1292
        prompt_suffix = hascolor ?
1293
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
1294
        repl = repl,
1295
        complete = replc,
1296
        on_enter = return_callback)
1297

1298
    # Setup help mode
1299
    help_mode = Prompt(contextual_prompt(repl, HELP_PROMPT),
×
1300
        prompt_prefix = hascolor ? repl.help_color : "",
1301
        prompt_suffix = hascolor ?
1302
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
1303
        repl = repl,
1304
        complete = replc,
1305
        # When we're done transform the entered line into a call to helpmode function
1306
        on_done = respond(line::String->helpmode(outstream(repl), line, repl.mistate.active_module),
×
1307
                          repl, julia_prompt, pass_empty=true, suppress_on_semicolon=false))
1308

1309

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

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

1355

1356
    ################################# Stage II #############################
1357

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

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

1390

1391
    search_prompt, skeymap = LineEdit.setup_search_keymap(hp)
×
1392
    search_prompt.complete = LatexCompletions()
×
1393

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

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

1404
    repl_keymap = AnyDict(
×
1405
        ';' => function (s::MIState,o...)
×
1406
            if isempty(s) || position(LineEdit.buffer(s)) == 0
×
1407
                buf = copy(LineEdit.buffer(s))
×
1408
                transition(s, shell_mode) do
×
1409
                    LineEdit.state(s, shell_mode).input_buffer = buf
×
1410
                end
1411
            else
1412
                edit_insert(s, ';')
×
1413
                LineEdit.check_show_hint(s)
×
1414
            end
1415
        end,
1416
        '?' => function (s::MIState,o...)
×
1417
            if isempty(s) || position(LineEdit.buffer(s)) == 0
×
1418
                buf = copy(LineEdit.buffer(s))
×
1419
                transition(s, help_mode) do
×
1420
                    LineEdit.state(s, help_mode).input_buffer = buf
×
1421
                end
1422
            else
1423
                edit_insert(s, '?')
×
1424
                LineEdit.check_show_hint(s)
×
1425
            end
1426
        end,
1427
        ']' => function (s::MIState,o...)
×
1428
            if isempty(s) || position(LineEdit.buffer(s)) == 0
×
1429
                buf = copy(LineEdit.buffer(s))
×
1430
                transition(s, dummy_pkg_mode) do
×
1431
                    LineEdit.state(s, dummy_pkg_mode).input_buffer = buf
×
1432
                end
1433
                # load Pkg on another thread if available so that typing in the dummy Pkg prompt
1434
                # isn't blocked, but instruct the main REPL task to do the transition via s.async_channel
1435
                t_replswitch = Threads.@spawn begin
×
1436
                    REPLExt = load_pkg()
×
1437
                    if REPLExt isa Module && isdefined(REPLExt, :PkgCompletionProvider)
×
1438
                        put!(s.async_channel,
×
1439
                            function (s::MIState)
×
1440
                                LineEdit.mode(s) === dummy_pkg_mode || return :ok
×
1441
                                for mode in repl.interface.modes
×
1442
                                    if mode isa LineEdit.Prompt && mode.complete isa REPLExt.PkgCompletionProvider
×
1443
                                        buf = copy(LineEdit.buffer(s))
×
1444
                                        transition(s, mode) do
×
1445
                                            LineEdit.state(s, mode).input_buffer = buf
×
1446
                                        end
1447
                                        if !isempty(s)
×
1448
                                            @invokelatest(LineEdit.check_show_hint(s))
×
1449
                                        end
1450
                                        break
×
1451
                                    end
1452
                                end
×
1453
                                return :ok
×
1454
                            end
1455
                        )
1456
                    end
1457
                end
1458
                Base.errormonitor(t_replswitch)
×
1459
            else
1460
                edit_insert(s, ']')
×
1461
                LineEdit.check_show_hint(s)
×
1462
            end
1463
        end,
1464

1465
        # Bracketed Paste Mode
1466
        "\e[200~" => (s::MIState,o...)->begin
×
1467
            input = LineEdit.bracketed_paste(s) # read directly from s until reaching the end-bracketed-paste marker
×
1468
            sbuffer = LineEdit.buffer(s)
×
1469
            curspos = position(sbuffer)
×
1470
            seek(sbuffer, 0)
×
1471
            shouldeval = (bytesavailable(sbuffer) == curspos && !occursin(UInt8('\n'), sbuffer))
×
1472
            seek(sbuffer, curspos)
×
1473
            if curspos == 0
×
1474
                # if pasting at the beginning, strip leading whitespace
1475
                input = lstrip(input)
×
1476
            end
1477
            if !shouldeval
×
1478
                # when pasting in the middle of input, just paste in place
1479
                # don't try to execute all the WIP, since that's rather confusing
1480
                # and is often ill-defined how it should behave
1481
                edit_insert(s, input)
×
1482
                return
×
1483
            end
1484
            LineEdit.push_undo(s)
×
1485
            edit_insert(sbuffer, input)
×
1486
            input = String(take!(sbuffer))
×
1487
            oldpos = firstindex(input)
×
1488
            firstline = true
×
1489
            isprompt_paste = false
×
1490
            curr_prompt_len = 0
×
1491
            pasting_help = false
×
1492

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

1605
        # Open the editor at the location of a stackframe or method
1606
        # This is accessing a contextual variable that gets set in
1607
        # the show_backtrace and show_method_table functions.
1608
        "^Q" => (s::MIState, o...) -> begin
×
1609
            linfos = repl.last_shown_line_infos
×
1610
            str = String(take!(LineEdit.buffer(s)))
×
1611
            n = tryparse(Int, str)
×
1612
            n === nothing && @goto writeback
×
1613
            if n <= 0 || n > length(linfos) || startswith(linfos[n][1], "REPL[")
×
1614
                @goto writeback
×
1615
            end
1616
            try
×
1617
                InteractiveUtils.edit(Base.fixup_stdlib_path(linfos[n][1]), linfos[n][2])
×
1618
            catch ex
1619
                ex isa ProcessFailedException || ex isa Base.IOError || ex isa SystemError || rethrow()
×
1620
                @info "edit failed" _exception=ex
×
1621
            end
1622
            LineEdit.refresh_line(s)
×
1623
            return
×
1624
            @label writeback
×
1625
            write(LineEdit.buffer(s), str)
×
1626
            return
×
1627
        end,
1628
    )
1629

1630
    prefix_prompt, prefix_keymap = LineEdit.setup_prefix_keymap(hp, julia_prompt)
×
1631

1632
    a = Dict{Any,Any}[skeymap, repl_keymap, prefix_keymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults]
×
1633
    prepend!(a, extra_repl_keymap)
×
1634

1635
    julia_prompt.keymap_dict = LineEdit.keymap(a)
×
1636

1637
    mk = mode_keymap(julia_prompt)
×
1638

1639
    b = Dict{Any,Any}[skeymap, mk, prefix_keymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults]
×
1640
    prepend!(b, extra_repl_keymap)
×
1641

1642
    shell_mode.keymap_dict = help_mode.keymap_dict = dummy_pkg_mode.keymap_dict = LineEdit.keymap(b)
×
1643

1644
    allprompts = LineEdit.TextInterface[julia_prompt, shell_mode, help_mode, dummy_pkg_mode, search_prompt, prefix_prompt]
×
1645
    return ModalInterface(allprompts)
×
1646
end
1647

1648
function run_frontend(repl::LineEditREPL, backend::REPLBackendRef)
×
1649
    repl.frontend_task = current_task()
×
1650
    d = REPLDisplay(repl)
×
1651
    dopushdisplay = repl.specialdisplay === nothing && !in(d,Base.Multimedia.displays)
×
1652
    dopushdisplay && pushdisplay(d)
×
1653
    if !isdefined(repl,:interface)
×
1654
        interface = repl.interface = setup_interface(repl)
×
1655
    else
1656
        interface = repl.interface
×
1657
    end
1658
    repl.backendref = backend
×
1659
    repl.mistate = LineEdit.init_state(terminal(repl), interface)
×
1660
    run_interface(terminal(repl), interface, repl.mistate)
×
1661
    # Terminate Backend
1662
    put!(backend.repl_channel, (nothing, -1))
×
1663
    dopushdisplay && popdisplay(d)
×
1664
    nothing
×
1665
end
1666

1667
## StreamREPL ##
1668

1669
mutable struct StreamREPL <: AbstractREPL
1670
    stream::IO
1671
    prompt_color::String
1672
    input_color::String
1673
    answer_color::String
1674
    waserror::Bool
1675
    frontend_task::Task
1676
    StreamREPL(stream,pc,ic,ac) = new(stream,pc,ic,ac,false)
×
1677
end
1678
StreamREPL(stream::IO) = StreamREPL(stream, Base.text_colors[:green], Base.input_color(), Base.answer_color())
×
1679
run_repl(stream::IO) = run_repl(StreamREPL(stream))
×
1680

1681
outstream(s::StreamREPL) = s.stream
×
1682
hascolor(s::StreamREPL) = get(s.stream, :color, false)::Bool
×
1683

1684
answer_color(r::LineEditREPL) = r.envcolors ? Base.answer_color() : r.answer_color
×
1685
answer_color(r::StreamREPL) = r.answer_color
×
1686
input_color(r::LineEditREPL) = r.envcolors ? Base.input_color() : r.input_color
×
1687
input_color(r::StreamREPL) = r.input_color
×
1688

1689
let matchend = Dict("\"" => r"\"", "\"\"\"" => r"\"\"\"", "'" => r"'",
1690
    "`" => r"`", "```" => r"```", "#" => r"$"m, "#=" => r"=#|#=")
1691
    global _rm_strings_and_comments
1692
    function _rm_strings_and_comments(code::Union{String,SubString{String}})
×
1693
        buf = IOBuffer(sizehint = sizeof(code))
×
1694
        pos = 1
×
1695
        while true
×
1696
            i = findnext(r"\"(?!\"\")|\"\"\"|'|`(?!``)|```|#(?!=)|#=", code, pos)
×
1697
            isnothing(i) && break
×
1698
            match = SubString(code, i)
×
1699
            j = findnext(matchend[match]::Regex, code, nextind(code, last(i)))
×
1700
            if match == "#=" # possibly nested
×
1701
                nested = 1
×
1702
                while j !== nothing
×
1703
                    nested += SubString(code, j) == "#=" ? +1 : -1
×
1704
                    iszero(nested) && break
×
1705
                    j = findnext(r"=#|#=", code, nextind(code, last(j)))
×
1706
                end
×
1707
            elseif match[1] != '#' # quote match: check non-escaped
×
1708
                while j !== nothing
×
1709
                    notbackslash = findprev(!=('\\'), code, prevind(code, first(j)))::Int
×
1710
                    isodd(first(j) - notbackslash) && break # not escaped
×
1711
                    j = findnext(matchend[match]::Regex, code, nextind(code, first(j)))
×
1712
                end
×
1713
            end
1714
            isnothing(j) && break
×
1715
            if match[1] == '#'
×
1716
                print(buf, SubString(code, pos, prevind(code, first(i))))
×
1717
            else
1718
                print(buf, SubString(code, pos, last(i)), ' ', SubString(code, j))
×
1719
            end
1720
            pos = nextind(code, last(j))
×
1721
        end
×
1722
        print(buf, SubString(code, pos, lastindex(code)))
×
1723
        return String(take!(buf))
×
1724
    end
1725
end
1726

1727
# heuristic function to decide if the presence of a semicolon
1728
# at the end of the expression was intended for suppressing output
1729
ends_with_semicolon(code::AbstractString) = ends_with_semicolon(String(code))
×
1730
ends_with_semicolon(code::Union{String,SubString{String}}) =
×
1731
    contains(_rm_strings_and_comments(code), r";\s*$")
×
1732

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

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

1753
    commit_date = isempty(Base.GIT_VERSION_INFO.date_string) ? "" : " ($(split(Base.GIT_VERSION_INFO.date_string)[1]))"
×
1754

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

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

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

1798
            """)
1799
        end
1800
    end
1801
end
1802

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

1834
module Numbered
1835

1836
using ..REPL
1837

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

1840
function repl_eval_counter(hp)
×
1841
    return length(hp.history) - hp.start_idx
×
1842
end
1843

1844
function out_transform(@nospecialize(x), n::Ref{Int})
×
1845
    return Expr(:toplevel, get_usings!([], x)..., quote
×
1846
        let __temp_val_a72df459 = $x
×
1847
            $capture_result($n, __temp_val_a72df459)
×
1848
            __temp_val_a72df459
×
1849
        end
1850
    end)
1851
end
1852

1853
function get_usings!(usings, ex)
×
1854
    ex isa Expr || return usings
×
1855
    # get all `using` and `import` statements which are at the top level
1856
    for (i, arg) in enumerate(ex.args)
×
1857
        if Base.isexpr(arg, :toplevel)
×
1858
            get_usings!(usings, arg)
×
1859
        elseif Base.isexpr(arg, [:using, :import])
×
1860
            push!(usings, popat!(ex.args, i))
×
1861
        end
1862
    end
×
1863
    return usings
×
1864
end
1865

1866
function create_global_out!(mod)
×
1867
    if !isdefinedglobal(mod, :Out)
×
1868
        out = Dict{Int, Any}()
×
1869
        @eval mod begin
×
1870
            const Out = $(out)
×
1871
            export Out
×
1872
        end
1873
        return out
×
1874
    end
1875
    return getglobal(mod, Out)
×
1876
end
1877

1878
function capture_result(n::Ref{Int}, @nospecialize(x))
×
1879
    n = n[]
×
1880
    mod = Base.MainInclude
×
1881
    # TODO: This invokelatest is only required due to backdated constants
1882
    # and should be removed after
1883
    out = isdefinedglobal(mod, :Out) ? invokelatest(getglobal, mod, :Out) : invokelatest(create_global_out!, mod)
×
1884
    if x !== out && x !== nothing # remove this?
×
1885
        out[n] = x
×
1886
    end
1887
    nothing
×
1888
end
1889

1890
function set_prompt(repl::LineEditREPL, n::Ref{Int})
×
1891
    julia_prompt = repl.interface.modes[1]
×
1892
    julia_prompt.prompt = function()
×
1893
        n[] = repl_eval_counter(julia_prompt.hist)+1
×
1894
        string("In [", n[], "]: ")
×
1895
    end
1896
    nothing
×
1897
end
1898

1899
function set_output_prefix(repl::LineEditREPL, n::Ref{Int})
×
1900
    julia_prompt = repl.interface.modes[1]
×
1901
    if REPL.hascolor(repl)
×
1902
        julia_prompt.output_prefix_prefix = Base.text_colors[:red]
×
1903
    end
1904
    julia_prompt.output_prefix = () -> string("Out[", n[], "]: ")
×
1905
    nothing
×
1906
end
1907

1908
function __current_ast_transforms(backend)
×
1909
    if backend === nothing
×
1910
        Base.active_repl_backend !== nothing ? Base.active_repl_backend.ast_transforms : REPL.repl_ast_transforms
×
1911
    else
1912
        backend.ast_transforms
×
1913
    end
1914
end
1915

1916
function numbered_prompt!(repl::LineEditREPL=Base.active_repl::LineEditREPL, backend=nothing)
×
1917
    n = Ref{Int}(0)
×
1918
    set_prompt(repl, n)
×
1919
    set_output_prefix(repl, n)
×
1920
    push!(__current_ast_transforms(backend), @nospecialize(ast) -> out_transform(ast, n))
×
1921
    return
×
1922
end
1923

1924
"""
1925
    Out[n]
1926

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

1930
See also [`ans`](@ref).
1931
"""
1932
Base.MainInclude.Out
1933

1934
end
1935

1936
import .Numbered.numbered_prompt!
1937

1938
# this assignment won't survive precompilation,
1939
# but will stick if REPL is baked into a sysimg.
1940
# Needs to occur after this module is finished.
1941
Base.REPL_MODULE_REF[] = REPL
1942

1943
if Base.generating_output()
1944
    include("precompile.jl")
1945
end
1946

1947
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