Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split REPLMode into an package extension called REPLExt #3724

Closed
wants to merge 10 commits into from
9 changes: 7 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce"
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
Expand All @@ -24,6 +23,12 @@ Tar = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
p7zip_jll = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"

[weakdeps]
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[extensions]
REPLExt = "REPL"

[compat]
HistoricalStdlibVersions = "1.2"

Expand All @@ -33,4 +38,4 @@ Preferences = "21216c6a-2e73-6563-6e65-726566657250"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "Preferences", "HistoricalStdlibVersions"]
test = ["Test", "Preferences", "HistoricalStdlibVersions", "REPL"]
1 change: 1 addition & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[compat]
Documenter = "1"
4 changes: 2 additions & 2 deletions docs/generate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ function generate(io, command)
<section>
```
```@eval
using Pkg
Dict(Pkg.REPLMode.canonical_names())["$(command)"].help
using Pkg, REPL
Dict(Base.get_extension(Pkg, :REPLExt).canonical_names())["$(command)"].help
```
```@raw html
</section>
Expand Down
143 changes: 129 additions & 14 deletions src/REPLMode/REPLMode.jl → ext/REPLExt/REPLExt.jl
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

module REPLMode
module REPLExt

using Markdown, UUIDs, Dates

import REPL
import REPL: LineEdit, REPLCompletions
import REPL: TerminalMenus

import ..casesensitive_isdir, ..OFFLINE_MODE, ..linewrap, ..pathrepr
using ..Types, ..Operations, ..API, ..Registry, ..Resolve
import ..stdout_f, ..stderr_f
import Pkg: compat, @pkg_str
import Pkg: casesensitive_isdir, OFFLINE_MODE, linewrap, pathrepr
using Pkg: Types, Operations, API, Registry, Resolve
using Pkg: PackageSpec
import Pkg: stdout_f, stderr_f

using Pkg.Types: PKGMODE_PROJECT, PKGMODE_MANIFEST, UPLEVEL_MAJOR, UPLEVEL_MINOR, UPLEVEL_PATCH, UPLEVEL_FIXED
using Pkg.Types: PreserveLevel, PRESERVE_TIERED_INSTALLED, PRESERVE_TIERED, PRESERVE_ALL_INSTALLED, PRESERVE_ALL, PRESERVE_DIRECT, PRESERVE_SEMVER, PRESERVE_NONE
using Pkg.Types: Context, PkgError, pkgerror, EnvCache

const TEST_MODE = Ref{Bool}(false)
const PRINTED_REPL_WARNING = Ref{Bool}(false)
Expand All @@ -32,7 +38,7 @@ end

# TODO assert names matching lex regex
# assert now so that you don't fail at user time
# see function `REPLMode.api_options`
# see function `REPLExt.api_options`
function OptionSpec(;name::String,
short_name::Union{Nothing,String}=nothing,
takes_arg::Bool=false,
Expand Down Expand Up @@ -450,7 +456,7 @@ end
######################

# Provide a string macro pkg"cmd" that can be used in the same way
# as the REPLMode `pkg> cmd`. Useful for testing and in environments
# as the REPLExt `pkg> cmd`. Useful for testing and in environments
# where we do not have a REPL, e.g. IJulia.
struct MiniREPL <: REPL.AbstractREPL
display::TextDisplay
Expand All @@ -463,14 +469,22 @@ REPL.REPLDisplay(repl::MiniREPL) = repl.display

const minirepl = Ref{MiniREPL}()

__init__() = minirepl[] = MiniREPL()

macro pkg_str(str::String)
:($(do_cmd)(minirepl[], $str; do_rethrow=true))
end

pkgstr(str::String) = do_cmd(minirepl[], str; do_rethrow=true)

function __init__()
minirepl[] = MiniREPL()
if isdefined(Base, :active_repl)
repl_init(Base.active_repl)
else
atreplinit(repl_init)
end
push!(empty!(REPL.install_packages_hooks), try_prompt_pkg_add)
end

struct PkgCompletionProvider <: LineEdit.CompletionProvider end

function LineEdit.complete_line(c::PkgCompletionProvider, s)
Expand Down Expand Up @@ -534,7 +548,7 @@ function promptf()
return "$(prefix)pkg> "
end

# Set up the repl Pkg REPLMode
# Set up the repl REPLExt
function create_mode(repl::REPL.AbstractREPL, main::LineEdit.Prompt)
pkg_mode = LineEdit.Prompt(promptf;
prompt_prefix = repl.options.hascolor ? Base.text_colors[:blue] : "",
Expand Down Expand Up @@ -563,7 +577,7 @@ function create_mode(repl::REPL.AbstractREPL, main::LineEdit.Prompt)
mk = REPL.mode_keymap(main)

shell_mode = nothing
for mode in Base.active_repl.interface.modes
for mode in repl.interface.modes
if mode isa LineEdit.Prompt
mode.prompt == "shell> " && (shell_mode = mode)
end
Expand Down Expand Up @@ -593,7 +607,13 @@ function create_mode(repl::REPL.AbstractREPL, main::LineEdit.Prompt)
return pkg_mode
end

function repl_init(repl::REPL.AbstractREPL)
function repl_init(repl::REPL.LineEditREPL)
isdefined(repl, :interface) || (repl.interface = REPL.setup_interface(repl))
_repl_init(repl)
end
repl_init(repl::REPL.AbstractREPL) = _repl_init(repl)

function _repl_init(repl::REPL.AbstractREPL)
main_mode = repl.interface.modes[1]
pkg_mode = create_mode(repl, main_mode)
push!(repl.interface.modes, pkg_mode)
Expand Down Expand Up @@ -701,12 +721,12 @@ function try_prompt_pkg_add(pkgs::Vector{Symbol})
printstyled(ctx.io, " │ "; color=:green)
println(ctx.io, "Install package$(plural4)?")
msg2 = string("add ", join(available_pkgs, ' '))
for (i, line) in pairs(linewrap(msg2; io = ctx.io, padding = length(string(" | ", REPLMode.promptf()))))
for (i, line) in pairs(linewrap(msg2; io = ctx.io, padding = length(string(" | ", REPLExt.promptf()))))
printstyled(ctx.io, " │ "; color=:green)
if i == 1
printstyled(ctx.io, REPLMode.promptf(); color=:blue)
printstyled(ctx.io, REPLExt.promptf(); color=:blue)
else
print(ctx.io, " "^length(REPLMode.promptf()))
print(ctx.io, " "^length(REPLExt.promptf()))
end
println(ctx.io, line)
end
Expand Down Expand Up @@ -779,4 +799,99 @@ function try_prompt_pkg_add(pkgs::Vector{Symbol})
end
end

function compat(ctx::Context; io = nothing)
io = something(io, ctx.io)
can_fancyprint(io) || pkgerror("Pkg.compat cannot be run interactively in this terminal")
printpkgstyle(io, :Compat, pathrepr(ctx.env.project_file))
longest_dep_len = max(5, length.(collect(keys(ctx.env.project.deps)))...)
opt_strs = String[]
opt_pkgs = String[]
compat_str = Operations.get_compat_str(ctx.env.project, "julia")
push!(opt_strs, Operations.compat_line(io, "julia", nothing, compat_str, longest_dep_len, indent = ""))
push!(opt_pkgs, "julia")
for (dep, uuid) in sort(collect(ctx.env.project.deps); by = x->x.first)
compat_str = Operations.get_compat_str(ctx.env.project, dep)
push!(opt_strs, Operations.compat_line(io, dep, uuid, compat_str, longest_dep_len, indent = ""))
push!(opt_pkgs, dep)
end
menu = TerminalMenus.RadioMenu(opt_strs, pagesize=length(opt_strs))
choice = try
TerminalMenus.request(" Select an entry to edit:", menu)
catch err
if err isa InterruptException # if ^C is entered
println(io)
return false
end
rethrow()
end
choice == -1 && return false
dep = opt_pkgs[choice]
current_compat_str = something(Operations.get_compat_str(ctx.env.project, dep), "")
resp = try
prompt = " Edit compat entry for $(dep):"
print(io, prompt)
buffer = current_compat_str
cursor = length(buffer)
start_pos = length(prompt) + 2
move_start = "\e[$(start_pos)G"
clear_to_end = "\e[0J"
ccall(:jl_tty_set_mode, Int32, (Ptr{Cvoid},Int32), stdin.handle, true)
while true
print(io, move_start, clear_to_end, buffer, "\e[$(start_pos + cursor)G")
inp = TerminalMenus._readkey(stdin)
if inp == '\r' # Carriage return
println(io)
break
elseif inp == '\x03' # cltr-C
println(io)
return
elseif inp == TerminalMenus.ARROW_RIGHT
cursor = min(length(buffer), cursor + 1)
elseif inp == TerminalMenus.ARROW_LEFT
cursor = max(0, cursor - 1)
elseif inp == TerminalMenus.HOME_KEY
cursor = (0)
elseif inp == TerminalMenus.END_KEY
cursor = length(buffer)
elseif inp == TerminalMenus.DEL_KEY
if cursor == 0
buffer = buffer[2:end]
elseif cursor < length(buffer)
buffer = buffer[1:cursor] * buffer[(cursor + 2):end]
end
elseif inp isa TerminalMenus.Key
# ignore all other escaped (multi-byte) keys
elseif inp == '\x7f' # backspace
if cursor == 1
buffer = buffer[2:end]
elseif cursor == length(buffer)
buffer = buffer[1:end - 1]
elseif cursor > 0
buffer = buffer[1:(cursor-1)] * buffer[(cursor + 1):end]
else
continue
end
cursor -= 1
else
if cursor == 0
buffer = inp * buffer
elseif cursor == length(buffer)
buffer = buffer * inp
else
buffer = buffer[1:cursor] * inp * buffer[(cursor + 1):end]
end
cursor += 1
end
end
buffer
finally
ccall(:jl_tty_set_mode, Int32, (Ptr{Cvoid},Int32), stdin.handle, false)
end
new_entry = strip(resp)
compat(ctx, dep, string(new_entry))
return
end

include("precompile.jl")

end #module
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ..isdir_nothrow, ..Registry.RegistrySpec, ..isurl
import Pkg: isdir_nothrow, Registry.RegistrySpec, isurl

struct PackageIdentifier
val::String
Expand Down
File renamed without changes.
28 changes: 28 additions & 0 deletions ext/REPLExt/precompile.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
let

struct FakeTerminal <: REPL.Terminals.UnixTerminal
in_stream::IOBuffer
out_stream::IOBuffer
err_stream::IOBuffer
hascolor::Bool
raw::Bool
FakeTerminal() = new(IOBuffer(), IOBuffer(), IOBuffer(), false, true)
end
REPL.raw!(::FakeTerminal, raw::Bool) = raw

function pkgreplmode_precompile()
REPLExt.__init__()
REPLExt.try_prompt_pkg_add(Symbol[:notapackage])
REPLExt.promptf()
term = FakeTerminal()
repl = REPL.LineEditREPL(term, true)
REPL.run_repl(repl)
REPLExt.repl_init(repl)
end

if Base.generating_output()
Base.Experimental.@force_compile
pkgreplmode_precompile()
end

end # let