Skip to content

Commit

Permalink
Add %include REPL magic for including a remote file (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
c42f authored Mar 9, 2022
1 parent e7d18a4 commit 761a59b
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 5 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "RemoteREPL"
uuid = "1bd9f7bb-701c-4338-bec7-ac987af7c555"
authors = ["Chris Foster <chris42f@gmail.com> and contributors"]
version = "0.2.14"
version = "0.2.15"

[deps]
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
Expand Down
11 changes: 11 additions & 0 deletions docs/src/howto.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@ Expr
3: Symbol b
```

## Include a remote file

To include a Julia source file from the client into the current module on the
remote side, use the `%include` REPL magic:

```julia
julia@localhost> %include some/file.jl
```

`%include` has tab completion for local paths on the client.

## Evaluate commands in another module

If your server process has state in another module, you can tell RemoteREPL to
Expand Down
47 changes: 43 additions & 4 deletions src/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ function parse_input(str)
end

function match_magic_syntax(str)
m = match(r"^(%module|\?) *(.*)", str)
m = match(r"^(%module|\?|%include) *(.*)", str)
if !isnothing(m)
return (m[1], m[2])
else
Expand All @@ -202,7 +202,11 @@ function valid_input_checker(prompt_state)
cmdstr = String(take!(copy(REPL.LineEdit.buffer(prompt_state))))
magic = match_magic_syntax(cmdstr)
if !isnothing(magic)
cmdstr = magic[2]
if magic[1] in ("%module", "?")
cmdstr = magic[2]
elseif magic[1] == "%include"
return true
end
end
ex = parse_input(cmdstr)
return !Meta.isexpr(ex, :incomplete)
Expand All @@ -212,14 +216,36 @@ struct RemoteCompletionProvider <: REPL.LineEdit.CompletionProvider
connection
end

function path_str(path_completion)
path = REPL.REPLCompletions.completion_text(path_completion)
if Sys.iswindows()
# On windows, REPLCompletions.complete_path() adds extra escapes for
# use within a normal string in the Juila REPL but we don't need those.
path = replace(path, "\\\\"=>'\\')
end
return path
end

function REPL.complete_line(provider::RemoteCompletionProvider,
state::REPL.LineEdit.PromptState)::
Tuple{Vector{String},String,Bool}
# See REPL.jl complete_line(c::REPLCompletionProvider, s::PromptState)
partial = REPL.beforecursor(state.input_buffer)
full = REPL.LineEdit.input_string(state)
if !isempty(full) && startswith("%modul", full)
return (["%module"], full, true)
if startswith(full, "%m")
if startswith("%module", full)
return (["%module "], full, true)
end
elseif startswith(full, "%i")
if startswith("%include", full)
return (["%include "], full, true)
elseif startswith(full, "%include ")
_, path_prefix = match_magic_syntax(full)
(path_completions, range, should_complete) =
REPL.REPLCompletions.complete_path(path_prefix, length(path_prefix))
completions = [path_str(c) for c in path_completions]
return (completions, path_prefix[range], should_complete)
end
end
result = ensure_connected!(provider.connection) do
send_and_receive(provider.connection, (:repl_completion, (partial, full)))
Expand Down Expand Up @@ -275,6 +301,19 @@ function run_remote_repl_command(conn, out_stream, cmdstr)
elseif magic[1] == "%module"
mod_ex = Meta.parse(magic[2])
cmd = (:in_module, mod_ex)
elseif magic[1] == "%include"
path = abspath(magic[2])
text = read(path, String)
# Some rough heuristics to construct a file URI. This gives us
# a place to put the host name.
if !startswith(path, '/')
path = '/'*path
end
if Sys.iswindows()
path = replace(path, '\\'=>'/')
end
path_uri = "file://$(gethostname())$path"
cmd = (:eval, :(Base.include_string(@__MODULE__, $text, $path_uri)))
end
end
messageid, value = send_and_receive(conn, cmd)
Expand Down
9 changes: 9 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ end
# Module setting
@test match_magic_syntax("%module xx") == ("%module", "xx")
@test match_magic_syntax(" %module xx") == nothing

# Remote include
@test match_magic_syntax("%include /path/to/file") == ("%include", "/path/to/file")
@test match_magic_syntax("%include /path /to/file") == ("%include", "/path /to/file")
end

@testset "Prompt text" begin
Expand Down Expand Up @@ -173,6 +177,11 @@ try
@test completion_msg[1] == :completion_result
@test completion_msg[2] == ([], "complete_m", true)

# Remote include
path = joinpath(@__DIR__, "to_include.jl")
@test runcommand("%include $path") == "12345"
@test runcommand("var_in_included_file") == "12345"

# Test the @remote macro
Main.eval(:(clientside_var = 0:41))
@test runcommand("serverside_var = 1 .+ @remote clientside_var") == "1:42"
Expand Down
1 change: 1 addition & 0 deletions test/to_include.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
var_in_included_file = 12345

2 comments on commit 761a59b

@c42f
Copy link
Collaborator Author

@c42f c42f commented on 761a59b Mar 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/56321

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.2.15 -m "<description of version>" 761a59b5c4e0b61b950b93a445fe14795ee67781
git push origin v0.2.15

Please sign in to comment.