How could a run macro be defined that executes a script?
It should just include the file. Would something like:
@run myscript.jl
be possible as replacement for
include("myscript.jl")
?
That would save 7 keystrokes in the REPL.
How could a run macro be defined that executes a script?
It should just include the file. Would something like:
@run myscript.jl
be possible as replacement for
include("myscript.jl")
?
That would save 7 keystrokes in the REPL.
macro run(script)
return :(include($script))
end
@run "scripts/tmp.jl"
I don’t see a way of avoiding the quotation marks in the invocation, however.
Function APIs are way more flexible, from my standpoint.
Just like JLD2.@load "a.jld2" can be replaced with first(JLD2.load("a.jld2")).second.
It’s mostly possible if we painstakingly validate an Expr with “variables” and “operators” like /, \, . etc, but it seems like a lot of trouble just to hit a ParseError at a very common C:\. If the point is to save keystrokes, then a single-character alias for include or a nonstandard string literal saves more. To edit your macro, the latter looks like:
julia> macro run_str(script)
esc( :(include($script)) )
end
julia> @macroexpand run"C:\blah.jl"
:(include("C:\\blah.jl"))
Note that non-standard string literals are parsed like raw strings (which are actually implemented with no processing) unlike normal Strings, so this doesn’t allow string interpolation or most unescaping.
@run my_file.jl wouldn’t TAB-complete in the REPL, which saves far more keystrokes and errors than this macro would IMHO.
I like the custom string literal most. Nice: After TAB completion, you do not need to type the closing bracket.
macro run_str(script)
esc( :(include($script)) )
end
I put it in my startup.jl file in the .julia/config folder.
With cheating you can save 9 instead of the 7 (isn’t it 6?) keystrokes by just entering the file name in the REPL and hitting alt+i:
using REPL: LineEdit
@async Base.active_repl.interface.modes[1].keymap_dict['\e']['i'] = function (state, _, _)
io = LineEdit.buffer(state)
write(io, "include(\"", io |> take! |> String, "\")")
LineEdit.commit_line(state)
return :done
end
Replace ['i'] by ['r'] if you want to indicate run instead of include. The @async supports adding this to startup.jl. And obviously includet might be more useful than include. However, @cstjean’s point of missing TAB-complete still stands.
This is tested with Julia 1.11.7 and 1.12.1. Expect breakage with other Julia versions (as e.g. seen on nightly) due to using private identifiers.
I am not sure how seriously I am suggesting this. But I do think we should consider using REPL shortcuts more often in non-composable use cases like this.
This sounded too much of a challenge to let it pass. So with
using REPL
using REPL.REPLCompletions: completions, complete_path, PathCompletion, ModuleCompletion
const completions_orig_world = Base.get_world_counter()
function REPL.REPLCompletions.completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true, hint::Bool=false)
# Get original completions.
ret, range, should_complete = Base.invoke_in_world(completions_orig_world, completions, string, pos, context_module, shift, hint)
# If we don't already have path completions, add them.
has_paths = any(x -> x isa PathCompletion, ret)
if !has_paths
paths, dir, path_success = complete_path(string[1:pos])
if path_success
# In subfolder completion, do not suggest anything else than paths.
contains(string[1:pos], '/') && filter!(x -> x isa PathCompletion, ret)
# In subfolder completion, do not suggest strange entries.
filter!(x -> !(x isa ModuleCompletion && x.parent == Main && x.mod in ("/", "//")), ret)
# Join directory with paths.
paths = (PathCompletion(joinpath(dir, p.path)) for p in paths)
append!(ret, paths)
range = 1:pos
end
end
return ret, range, should_complete
end
it also TAB-completes here in 1.12.1. There are many reasons why this is dirty and yet I find it fascinating. Maybe we should think about making more of completions a public API? It would be interesting to see more packages adding specific completions.
Just to list the ones that come to my mind:
include call in a file. Generally, a simple public function could be provided for scripts, and the REPL’s generated code would just call it.ALT + i does something different.ALT + i is too quick of a decision to generate and execute code, which is vulnerable to typos (ALT + o could do something else we’d rather not) or misremembering (was it ALT + t, nope, that swaps the file name and extension for some reason).I don’t know of a way to address all those at once to save keystrokes, and maybe there isn’t one. However, the last one could be improved with a custom prompt mode. We still wouldn’t see the generated code, but there is at least a visible indicator of its intention and we decide to do it separately.
julia> # ALT+i or whatever keystrokes are necessary
include> helloworld.jl # ENTER to go through with it
hello world
include> # ready for more files or backspace to return to julia> prompt
It’d be nice if custom TAB-completion can be isolated to a custom prompt mode instead of changing the builtin prompt modes, but I don’t know how.
In general providing functionality as key-combination would indeed be problematic, as it won’t compose. In this case, I think the public function already exists as include.
Packages do effectively already register a callback to keymap_dict. It does not need to be public, because only the packages need to know about it in the code itself.
This is already the case for all packages which add a REPL shortcut or a REPL mode (see e.g. TerminalPager.jl or Pkg.jl). It might make sense to add a coordination package for REPL shortcuts and for REPL modes similar to FileIO.jl.
Users should at least be able to activate/deactivate this feature. There are probably not too many users which have highly problematic scripts lying around in their working directory and there are are other key combinations which can in the worst case destroy some hours of work (e.g. ctrl+d). But I agree that this feature can be unparalleled in how much you can damage yourself with little effort.
It’d be nice if custom TAB-completion can be isolated to a custom prompt mode instead of changing the builtin prompt modes, but I don’t know how.
This is quite some work, but not too difficult when you use an existing implementation as a template, e.g. the TerminalPager.jl implementation. You create a new mode and either use your own CompletionProvider or add the shortcut to the REPL mode of choice. I used modes[1] above which is the regular REPL mode.
I opted against a REPL mode, because it’s both more work for the initial implementation and for the user. Additionally, I think some features which are currently implemented as a REPL mode are better implemented as a shortcut (see e.g. the help mode versus the inline help feature of TerminalPager.jl). However, the best solution would probably be to offer both and let the user decide. We already see that preferences vary for this include feature.
There are many reasons why this is dirty
I had some additional reasons in my mind, e.g.
REPL.REPLCompletions.completions method, which has a fully specific signature, by using world age is interesting and might be worth its own macro(s) (and possibly package).completions already found some, but I am not sure./ to see whether we already have a path is neither sufficient (e.g. division) nor necessary (e.g. Windows) for having a path.filter! was unnecessary, but I do not know why these entries are generated at all and how to avoid this.