How can I run hooks before and after the execution of a command in the REPL?

I first opened an issue on VSCode’s Julia extension to make a bell sound after a long-running command. After more thought, I reached the conclusion that having hooks that run arbitrary Julia code before and after command execution is the correct approach (zsh and emacs use similar solutions). But the REPL hooks are not related to the VSCode extension, and so this should be a Julia feature, not an editor feature. Is there a way to do this now? If not, can the necessary hooks be added?

PS: If it isn’t clear, here’s what I want to do with hooks to solve the original problem of sounding a bell for longrunning commands:

I want to record the time before any command is executed in the REPL, and after the execution finished, see the duration the command took. If this duration is larger than a threshold, I’ll then start a bell sound.

PPS: Since the VSCode extension does some weird background stuff with the REPL, it still probably needs to do some work to support these hooks properly for its background code execution features. But foreground execution should work without any change on their part.

1 Like

I have not used it but there is

This is a cool package, but it’s for sending a notification. I need a hook in the REPL machinary itself to actually use this package automatically. I want to record the time before any command is executed in the REPL, and after the execution finished, see the duration the command took. If this duration is larger than a threshold, I’ll then use a notification command (here is where Notifier.jl can be useful.).

AFAIU Revise is hooking into the REPL in order to automatically run
Revise.revise() before each command. You might want to look at how it’s done there.

PS: sorry for the short & incomplete answer; I’m not at my computer right now…

1 Like

I think it is this part:

I don’t know if this is documented API though. Looks like it changed for 1.5 to make this easier.

2 Likes

There’s two kinds of hooks I can imagine people wanting:

  1. Run some function before/after each REPL input/output.
  2. Transform each REPL AST input before evaluating it.

Of course, the latter is strictly more general since you can insert calls before and after the actual input expression to call hooks, but the former API is much simpler, so it may make some sense to have both? Would be worth filing an issue to discuss making this simpler and/or more official.

3 Likes

Yes, this would be great. I accomplished my goals using @Tamas_Papp’s help, but making the API official would be good:

using REPL
function repl_pre()

end
function repl_post()

end
function repl_transform_prepost(ex)
    res_sym = gensym()
    Expr(:toplevel, :($repl_pre()), :($res_sym = $ex),:($repl_post()),:($res_sym))
end
if isdefined(Base, :active_repl_backend)
    if VERSION >= v"1.5.0-DEV.282"
        pushfirst!(Base.active_repl_backend.ast_transforms, repl_transform_prepost)
    else
        # Unsupported
    end
elseif isdefined(Main, :IJulia)
    # Unsupported
    Main.IJulia.push_preexecute_hook(repl_pre)
elseif VERSION >= v"1.5.0-DEV.282"
    pushfirst!(REPL.repl_ast_transforms, repl_transform_prepost)
end

That sounds a lot like a REPL mode, https://github.com/MasonProtter/ReplMaker.jl

3 Likes

Kind of, but you might want to modify and existing repl mode instead of adding a new one.

2 Likes

This isn’t currently documented, but ast_transforms is intended precisely to make it easier to hook into the REPL in a composable fashion. We should probably document it, but in the meantime I would still encourage you to use it for your REPL hooks with Julia ≥ 1.5.

I plan to support it in IJulia soon.

3 Likes

I don’t have a strong preference here, but since a chain of AST transformations is more general, I guess that would be enough, with the understanding that they will participate in a foldl-like reduction.

@stevengj: it would be great to have this documented. Can you please open an issue?

Done: https://github.com/JuliaLang/julia/issues/37047

2 Likes