From b76197c178a3ee2a29563d4d10422cf99d58e492 Mon Sep 17 00:00:00 2001 From: c42f Date: Thu, 27 Apr 2023 20:25:14 +1000 Subject: [PATCH] Run OhMyREPL keybindings in a fixed world age This should avoid most invalidations caused by loading other packages. Also remove the NEW_KEYBINDINGS global as it's hard to follow where this is added to. Rearrange all overrides so the implementation code can be precompiled, with the only thing which is `eval`d at runtime being the method pirating itself. Include global mutable "hook" binding for `LineEdit.refresh_line` to allow us to fix the world age of the implementation. --- src/BracketInserter.jl | 1 - src/OhMyREPL.jl | 57 ++++++++++++++---------- src/{MarkdownHighlighter.jl => hooks.jl} | 55 ++++++++++++++++++++--- src/output_prompt_overwrite.jl | 28 ------------ src/precompile.jl | 5 +++ src/refresh_lines.jl | 6 --- src/repl.jl | 37 ++++++++------- 7 files changed, 108 insertions(+), 81 deletions(-) rename src/{MarkdownHighlighter.jl => hooks.jl} (57%) delete mode 100644 src/output_prompt_overwrite.jl delete mode 100644 src/refresh_lines.jl diff --git a/src/BracketInserter.jl b/src/BracketInserter.jl index 90a98260..73a533ba 100644 --- a/src/BracketInserter.jl +++ b/src/BracketInserter.jl @@ -166,5 +166,4 @@ function insert_into_keymap!(D::Dict) end end -insert_into_keymap!(OhMyREPL.Prompt.NEW_KEYBINDINGS) end # module diff --git a/src/OhMyREPL.jl b/src/OhMyREPL.jl index 07cb58c8..3a46e223 100644 --- a/src/OhMyREPL.jl +++ b/src/OhMyREPL.jl @@ -17,12 +17,26 @@ export colorscheme!, colorschemes, enable_autocomplete_brackets, enable_highligh const SUPPORTS_256_COLORS = !(Sys.iswindows() && VERSION < v"1.5.3") +# Wrap the function `f` so that it's always invoked in the given `world_age` +function fix_world_age(f, world_age) + if world_age == typemax(UInt) + function (args...; kws...) + Base.invokelatest(f, args...; kws...) + end + else + function (args...; kws...) + Base.invoke_in_world(world_age, f, args...; kws...) + end + end +end + include("repl_pass.jl") include("repl.jl") include("passes/Passes.jl") include("BracketInserter.jl") include("prompt.jl") +include("hooks.jl") import .BracketInserter.enable_autocomplete_brackets @@ -91,50 +105,47 @@ const ENABLE_FZF = Ref(true) enable_fzf(v::Bool) = ENABLE_FZF[] = v using Pkg -function reinsert_after_pkg() - repl = Base.active_repl +function reinsert_after_pkg(repl, world_age) mirepl = isdefined(repl,:mi) ? repl.mi : repl main_mode = mirepl.interface.modes[1] m = first(methods(main_mode.keymap_dict[']'])) if m.module == Pkg.REPLMode - Prompt.insert_keybindings() + Prompt.insert_keybindings(repl, world_age) end end +function setup_repl(repl, world_age) + if !isdefined(repl, :interface) + repl.interface = REPL.setup_interface(repl) + end + Prompt.insert_keybindings(repl, world_age) + @async begin + sleep(0.25) + reinsert_after_pkg(repl, world_age) + end + update_interface(repl.interface) + + global _refresh_line_hook = fix_world_age(_refresh_line, world_age) +end + function __init__() options = Base.JLOptions() + world_age = Base.get_world_counter() # command-line if (options.isinteractive != 1) && options.commands != C_NULL return end if isdefined(Base, :active_repl) - if !isdefined(Base.active_repl, :interface) - Base.active_repl.interface = REPL.setup_interface(Base.active_repl) - end - Prompt.insert_keybindings() - @async begin - sleep(0.25) - reinsert_after_pkg() - end + setup_repl(Base.active_repl, world_age) else atreplinit() do repl - if !isdefined(repl, :interface) - repl.interface = REPL.setup_interface(repl) - end - Prompt.insert_keybindings() - @async begin - sleep(0.25) - reinsert_after_pkg() - end - update_interface(repl.interface) + setup_repl(Base.active_repl, world_age) end end if ccall(:jl_generating_output, Cint, ()) == 0 - include(joinpath(@__DIR__, "refresh_lines.jl")) - include(joinpath(@__DIR__, "output_prompt_overwrite.jl")) - include(joinpath(@__DIR__, "MarkdownHighlighter.jl")) + activate_hooks() end end diff --git a/src/MarkdownHighlighter.jl b/src/hooks.jl similarity index 57% rename from src/MarkdownHighlighter.jl rename to src/hooks.jl index 6736bf25..24d7c3f4 100644 --- a/src/MarkdownHighlighter.jl +++ b/src/hooks.jl @@ -1,13 +1,41 @@ - +import REPL +import REPL.LineEdit using Crayons import Markdown -import .OhMyREPL.Passes.SyntaxHighlighter.SYNTAX_HIGHLIGHTER_SETTINGS -import .OhMyREPL.HIGHLIGHT_MARKDOWN +function _refresh_line(s::REPL.LineEdit.BufferLike) + LineEdit.refresh_multi_line(s) + OhMyREPL.Prompt.rewrite_with_ANSI(s) +end + +function _REPL_display(d::REPL.REPLDisplay, mime::MIME"text/plain", @nospecialize(x)) + x = Ref{Any}(x) + REPL.with_repl_linfo(d.repl) do io + if isdefined(REPL, :active_module) + mod = REPL.active_module(d)::Module + else + mod = Main + end + io = IOContext(io, :limit => true, :module => mod) + if OUTPUT_PROMPT !== nothing + output_prompt = OUTPUT_PROMPT isa String ? OUTPUT_PROMPT : OUTPUT_PROMPT() + write(io, OUTPUT_PROMPT_PREFIX) + write(io, output_prompt, "\e[0m") + end + get(io, :color, false) && write(io, REPL.answer_color(d.repl)) + if isdefined(d.repl, :options) && isdefined(d.repl.options, :iocontext) + # this can override the :limit property set initially + io = foldl(IOContext, d.repl.options.iocontext, init=io) + end + show(io, mime, x[]) + println(io) + end + return nothing +end split_lines(s::AbstractString) = isdefined(Markdown, :lines) ? Markdown.lines(s) : split(s, '\n') -function Markdown.term(io::IO, md::Markdown.Code, columns) +function _Markdown_term(io::IO, md::Markdown.Code, columns) code = md.code # Want to remove potential. lang = md.language == "" ? "" : first(split(md.language)) @@ -37,11 +65,11 @@ function Markdown.term(io::IO, md::Markdown.Code, columns) push!(outputs, "") end - if do_syntax && HIGHLIGHT_MARKDOWN[] + if do_syntax && OhMyREPL.HIGHLIGHT_MARKDOWN[] for (i, (sourcecode, output)) in enumerate(zip(sourcecodes, outputs)) tokens = collect(tokenize(sourcecode)) crayons = fill(Crayon(), length(tokens)) - SYNTAX_HIGHLIGHTER_SETTINGS(crayons, tokens, 0, sourcecode) + OhMyREPL.Passes.SyntaxHighlighter.SYNTAX_HIGHLIGHTER_SETTINGS(crayons, tokens, 0, sourcecode) buff = IOBuffer() if lang == "jldoctest" || lang == "julia-repl" print(buff, Crayon(foreground = :red, bold = true), "julia> ", Crayon(reset = true)) @@ -72,3 +100,18 @@ function Markdown.term(io::IO, md::Markdown.Code, columns) end end end + +_refresh_line_hook = _refresh_line + +function activate_hooks() + @eval begin + LineEdit.refresh_line(s::REPL.LineEdit.BufferLike) = + _refresh_line_hook(s) + Markdown.term(io::IO, md::Markdown.Code, columns) = + _Markdown_term(io, md, columns) + end + if !isdefined(REPL, :IPython) + @eval REPL.display(d::REPL.REPLDisplay, mime::MIME"text/plain", x) = + _REPL_display(d, mime, x) + end +end diff --git a/src/output_prompt_overwrite.jl b/src/output_prompt_overwrite.jl deleted file mode 100644 index 33a5da05..00000000 --- a/src/output_prompt_overwrite.jl +++ /dev/null @@ -1,28 +0,0 @@ -import REPL - -if !isdefined(REPL, :IPython) -function REPL.display(d::REPL.REPLDisplay, mime::MIME"text/plain", x) - x = Ref{Any}(x) - REPL.with_repl_linfo(d.repl) do io - if isdefined(REPL, :active_module) - mod = REPL.active_module(d)::Module - else - mod = Main - end - io = IOContext(io, :limit => true, :module => mod) - if OUTPUT_PROMPT !== nothing - output_prompt = OUTPUT_PROMPT isa String ? OUTPUT_PROMPT : OUTPUT_PROMPT() - write(io, OUTPUT_PROMPT_PREFIX) - write(io, output_prompt, "\e[0m") - end - get(io, :color, false) && write(io, REPL.answer_color(d.repl)) - if isdefined(d.repl, :options) && isdefined(d.repl.options, :iocontext) - # this can override the :limit property set initially - io = foldl(IOContext, d.repl.options.iocontext, init=io) - end - show(io, mime, x[]) - println(io) - end - return nothing -end -end diff --git a/src/precompile.jl b/src/precompile.jl index ff76895e..1b5d38e8 100644 --- a/src/precompile.jl +++ b/src/precompile.jl @@ -5,3 +5,8 @@ precompile(Tuple{OhMyREPL.Passes.SyntaxHighlighter.SyntaxHighlighterSettings, Ar precompile(Tuple{OhMyREPL.Passes.BracketHighlighter.BracketHighlighterSettings, Array{Crayons.Crayon, 1}, Array{JuliaSyntax.Token, 1}, Int64, String}) precompile(Tuple{OhMyREPL.Passes.RainbowBrackets.RainbowBracketsSettings, Array{Crayons.Crayon, 1}, Array{JuliaSyntax.Token, 1}, Int64, String}) precompile(Tuple{typeof(OhMyREPL.untokenize_with_ANSI), Base.IOContext{Base.GenericIOBuffer{Array{UInt8, 1}}}, OhMyREPL.PassHandler, Array{JuliaSyntax.Token, 1}, String, Int64}) +precompile(_refresh_line, (REPL.LineEdit.ModeState,)) +precompile(_refresh_line, (REPL.LineEdit.MIState,)) +precompile(_refresh_line, (REPL.LineEdit.IOBuffer,)) +precompile(_REPL_display, (REPL.REPLDisplay, MIME"text/plain", String)) +precompile(_Markdown_term, (IO, Markdown.Code, Int)) diff --git a/src/refresh_lines.jl b/src/refresh_lines.jl deleted file mode 100644 index 00446e6e..00000000 --- a/src/refresh_lines.jl +++ /dev/null @@ -1,6 +0,0 @@ -import REPL.LineEdit - -function LineEdit.refresh_line(s::REPL.LineEdit.BufferLike) - LineEdit.refresh_multi_line(s) - OhMyREPL.Prompt.rewrite_with_ANSI(s) -end diff --git a/src/repl.jl b/src/repl.jl index c7ffd347..090925d7 100644 --- a/src/repl.jl +++ b/src/repl.jl @@ -17,7 +17,7 @@ import REPL.Terminals: raw!, width, height, cmove, getX, TerminalBuffer, getY, clear_line, beep, disable_bracketed_paste, enable_bracketed_paste using OhMyREPL -import OhMyREPL: untokenize_with_ANSI, apply_passes!, PASS_HANDLER +import OhMyREPL: untokenize_with_ANSI, apply_passes!, PASS_HANDLER, fix_world_age if VERSION > v"1.3" import JLFzf @@ -89,8 +89,7 @@ function rewrite_with_ANSI(s, cursormove::Bool = false) end end - -function create_keybindings() +function create_keybindings(prefix_hist_prompt, world_age) D = Dict{Any, Any}() D['\b'] = (s, data, c) -> if LineEdit.edit_backspace(s, true) rewrite_with_ANSI(s) @@ -289,29 +288,33 @@ function create_keybindings() LineEdit.enter_search(s, p, true) end end - return D + + # Up Arrow + D["\e[A"] = (s,o...)-> begin + LineEdit.edit_move_up(buffer(s)) || LineEdit.enter_prefix_search(s, prefix_hist_prompt, true) + Prompt.rewrite_with_ANSI(s) + end + # Down Arrow + D["\e[B"] = (s,o...)-> begin + LineEdit.edit_move_down(buffer(s)) || LineEdit.enter_prefix_search(s, prefix_hist_prompt, false) + Prompt.rewrite_with_ANSI(s) + end + + OhMyREPL.BracketInserter.insert_into_keymap!(D) + + return Dict(k=>fix_world_age(f, world_age) for (k,f) in D) end -NEW_KEYBINDINGS = create_keybindings() -function insert_keybindings(repl = Base.active_repl) +function insert_keybindings(repl, world_age) mirepl = (isdefined(repl,:mistate) && !isnothing(repl.mistate)) ? repl.mistate : repl interface_modes = mirepl.interface.modes main_mode = interface_modes[1] php_idx = findfirst(Base.Fix2(isa, LineEdit.PrefixHistoryPrompt), interface_modes) p = interface_modes[php_idx] - # Up Arrow - NEW_KEYBINDINGS["\e[A"] = (s,o...)-> begin - LineEdit.edit_move_up(buffer(s)) || LineEdit.enter_prefix_search(s, p, true) - Prompt.rewrite_with_ANSI(s) - end - # Down Arrow - NEW_KEYBINDINGS["\e[B"] = (s,o...)-> begin - LineEdit.edit_move_down(buffer(s)) || LineEdit.enter_prefix_search(s, p, false) - Prompt.rewrite_with_ANSI(s) - end + keybinds = create_keybindings(p, world_age) - main_mode.keymap_dict = LineEdit.keymap(Dict{Any, Any}[NEW_KEYBINDINGS, main_mode.keymap_dict]) + main_mode.keymap_dict = LineEdit.keymap(Dict{Any, Any}[keybinds, main_mode.keymap_dict]) end function _commit_line(s, data, c)