KristofferC / OhMyREPL.jl

Syntax highlighting and other enhancements for the Julia REPL
https://kristofferc.github.io/OhMyREPL.jl/latest/
Other
768 stars 60 forks source link

Edit current line in editor #282

Closed mreppen closed 1 year ago

mreppen commented 1 year ago

Some shells (at least bash and fish) have a feature to take the current line and modify it in an editor (C-x C-e in bash and M-e in fish) using a temp file. This would be wonderful in Julia, but I haven't found it.

Bash executes the line after editing. If this is implemented, I would prefer to just be dropped back to the repl without automatic execution. Fish instead returns to the repl with the edited line ready.

mbarbar commented 1 year ago

I put this together for myself.

In your .julia/config/startup.jl, add

import REPL
import REPL.LineEdit

# Open current input in editor and spit the result out.
function edit_in_editor(s::LineEdit.MIState)
    # Get current input dodgily through kill ring.
    original_length = length(s.kill_ring)
    LineEdit.edit_kill_region(s)
    text = ""
    if original_length < length(s.kill_ring)
        text = s.kill_ring[end]
        pop!(s.kill_ring)
    end

    # Write current input to temporary file.
    fname, fd = mktemp()
    write(fd, text)
    flush(fd)
    close(fd)

    succ = true
    try
        # Open temporary file in editor.
        editor = ENV["EDITOR"]
        run(`$editor $fname`)
    catch e
        println()
        println("Editor command failed.")
        if e isa KeyError
            println("Set 'EDITOR' environment variable.")
        end
        println("Reverting to original input.")
        if !(e isa KeyError)
            println("See '$fname' for (maybe) extended input.")
        end
        succ = false
    end

    # Put extended input back into REPL, but get the original if we failed.
    # Dodgily, again, with kill ring API.
    push!(s.kill_ring, succ ? read(open(fname), String) : text)
    LineEdit.edit_yank(s)
    pop!(s.kill_ring)

    # Don't delete file on failure so data is less likely lost.
    if succ
        rm(fname)
    end

    return
end

const extra_keys = Dict{Any,Any}(
    "^o" => (s, o...) -> edit_in_editor(s),
)

function customize_keys(repl)
    repl.interface = REPL.setup_interface(repl; extra_repl_keymap = extra_keys)
end

atreplinit(customize_keys)

Mapped to ctrl+o (that ^o). Use at your own risk, the way it grabs input and puts it back is quite gross and I'm not sure if it may mess up state. May also add a spurious new line.

KristofferC commented 1 year ago

This is supported natively in Julia 1.9 now with Alt + E so I will just defer to that.