Skip to content

Commit 761a59b

Browse files
authored
Add %include REPL magic for including a remote file (#38)
1 parent e7d18a4 commit 761a59b

File tree

5 files changed

+65
-5
lines changed

5 files changed

+65
-5
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "RemoteREPL"
22
uuid = "1bd9f7bb-701c-4338-bec7-ac987af7c555"
33
authors = ["Chris Foster <chris42f@gmail.com> and contributors"]
4-
version = "0.2.14"
4+
version = "0.2.15"
55

66
[deps]
77
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"

docs/src/howto.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,17 @@ Expr
7474
3: Symbol b
7575
```
7676

77+
## Include a remote file
78+
79+
To include a Julia source file from the client into the current module on the
80+
remote side, use the `%include` REPL magic:
81+
82+
```julia
83+
julia@localhost> %include some/file.jl
84+
```
85+
86+
`%include` has tab completion for local paths on the client.
87+
7788
## Evaluate commands in another module
7889

7990
If your server process has state in another module, you can tell RemoteREPL to

src/client.jl

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ function parse_input(str)
190190
end
191191

192192
function match_magic_syntax(str)
193-
m = match(r"^(%module|\?) *(.*)", str)
193+
m = match(r"^(%module|\?|%include) *(.*)", str)
194194
if !isnothing(m)
195195
return (m[1], m[2])
196196
else
@@ -202,7 +202,11 @@ function valid_input_checker(prompt_state)
202202
cmdstr = String(take!(copy(REPL.LineEdit.buffer(prompt_state))))
203203
magic = match_magic_syntax(cmdstr)
204204
if !isnothing(magic)
205-
cmdstr = magic[2]
205+
if magic[1] in ("%module", "?")
206+
cmdstr = magic[2]
207+
elseif magic[1] == "%include"
208+
return true
209+
end
206210
end
207211
ex = parse_input(cmdstr)
208212
return !Meta.isexpr(ex, :incomplete)
@@ -212,14 +216,36 @@ struct RemoteCompletionProvider <: REPL.LineEdit.CompletionProvider
212216
connection
213217
end
214218

219+
function path_str(path_completion)
220+
path = REPL.REPLCompletions.completion_text(path_completion)
221+
if Sys.iswindows()
222+
# On windows, REPLCompletions.complete_path() adds extra escapes for
223+
# use within a normal string in the Juila REPL but we don't need those.
224+
path = replace(path, "\\\\"=>'\\')
225+
end
226+
return path
227+
end
228+
215229
function REPL.complete_line(provider::RemoteCompletionProvider,
216230
state::REPL.LineEdit.PromptState)::
217231
Tuple{Vector{String},String,Bool}
218232
# See REPL.jl complete_line(c::REPLCompletionProvider, s::PromptState)
219233
partial = REPL.beforecursor(state.input_buffer)
220234
full = REPL.LineEdit.input_string(state)
221-
if !isempty(full) && startswith("%modul", full)
222-
return (["%module"], full, true)
235+
if startswith(full, "%m")
236+
if startswith("%module", full)
237+
return (["%module "], full, true)
238+
end
239+
elseif startswith(full, "%i")
240+
if startswith("%include", full)
241+
return (["%include "], full, true)
242+
elseif startswith(full, "%include ")
243+
_, path_prefix = match_magic_syntax(full)
244+
(path_completions, range, should_complete) =
245+
REPL.REPLCompletions.complete_path(path_prefix, length(path_prefix))
246+
completions = [path_str(c) for c in path_completions]
247+
return (completions, path_prefix[range], should_complete)
248+
end
223249
end
224250
result = ensure_connected!(provider.connection) do
225251
send_and_receive(provider.connection, (:repl_completion, (partial, full)))
@@ -275,6 +301,19 @@ function run_remote_repl_command(conn, out_stream, cmdstr)
275301
elseif magic[1] == "%module"
276302
mod_ex = Meta.parse(magic[2])
277303
cmd = (:in_module, mod_ex)
304+
elseif magic[1] == "%include"
305+
path = abspath(magic[2])
306+
text = read(path, String)
307+
# Some rough heuristics to construct a file URI. This gives us
308+
# a place to put the host name.
309+
if !startswith(path, '/')
310+
path = '/'*path
311+
end
312+
if Sys.iswindows()
313+
path = replace(path, '\\'=>'/')
314+
end
315+
path_uri = "file://$(gethostname())$path"
316+
cmd = (:eval, :(Base.include_string(@__MODULE__, $text, $path_uri)))
278317
end
279318
end
280319
messageid, value = send_and_receive(conn, cmd)

test/runtests.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ end
4242
# Module setting
4343
@test match_magic_syntax("%module xx") == ("%module", "xx")
4444
@test match_magic_syntax(" %module xx") == nothing
45+
46+
# Remote include
47+
@test match_magic_syntax("%include /path/to/file") == ("%include", "/path/to/file")
48+
@test match_magic_syntax("%include /path /to/file") == ("%include", "/path /to/file")
4549
end
4650

4751
@testset "Prompt text" begin
@@ -173,6 +177,11 @@ try
173177
@test completion_msg[1] == :completion_result
174178
@test completion_msg[2] == ([], "complete_m", true)
175179

180+
# Remote include
181+
path = joinpath(@__DIR__, "to_include.jl")
182+
@test runcommand("%include $path") == "12345"
183+
@test runcommand("var_in_included_file") == "12345"
184+
176185
# Test the @remote macro
177186
Main.eval(:(clientside_var = 0:41))
178187
@test runcommand("serverside_var = 1 .+ @remote clientside_var") == "1:42"

test/to_include.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
var_in_included_file = 12345

0 commit comments

Comments
 (0)