@@ -32,6 +32,51 @@ function simple_macro_expand!(func, ex, macro_name)
3232 return ex
3333end
3434
35+ # Parse all top level code from `path`, using a file:// URI as the file name.
36+ function parseall_with_file_urls(path)
37+ path = abspath(path)
38+ text = read(path, String)
39+ # Some rough heuristics to construct a file URI. This gives us
40+ # a place to put the host name.
41+ if ! startswith(path, ' /' )
42+ path = ' /' * path
43+ end
44+ if Sys. iswindows()
45+ path = replace(path, ' \\ ' => ' /' )
46+ end
47+ path_uri = " file://$(gethostname())$path "
48+ return VERSION >= v" 1.6" ?
49+ Meta. parseall(text, filename= path_uri) :
50+ Base. parse_input_line(text, filename= path_uri)
51+ end
52+
53+ # Replace simple occurances of `include(path)` at top level and module scope
54+ # when `path` is a string literal.
55+ function replace_includes!(ex, parentdir)
56+ if Meta. isexpr(ex, :call) && ex. args[1 ] == :include
57+ if length(ex. args) == 2 && ex. args[2 ] isa AbstractString
58+ p = joinpath(parentdir, ex. args[2 ])
59+ inc_ex = parseall_with_file_urls(p)
60+ replace_includes!(inc_ex, dirname(p))
61+ return inc_ex
62+ else
63+ error(" Path in expression `$ex ` must be a literal string to work with `%include`" )
64+ end
65+ elseif Meta. isexpr(ex, :toplevel)
66+ map!(e-> replace_includes!(e, parentdir), ex. args, ex. args)
67+ elseif Meta. isexpr(ex, :module)
68+ map!(e-> replace_includes!(e, parentdir), ex. args[3 ]. args, ex. args[3 ]. args)
69+ end
70+ return ex
71+ end
72+
73+ # Parse the code in `path` and recursively replace occurances of
74+ # `include(path)` with the parsed code from that path.
75+ function parse_and_replace_includes(path)
76+ path = abspath(path)
77+ ex = replace_includes!(parseall_with_file_urls(path), dirname(path))
78+ end
79+
3580# Read and verify header bytes on initializing the connection
3681function verify_header(io, ser_version= Serialization. ser_version)
3782 magic = String(read(io, length(PROTOCOL_MAGIC)))
@@ -257,7 +302,46 @@ function REPL.complete_line(provider::RemoteCompletionProvider,
257302end
258303
259304function run_remote_repl_command(conn, out_stream, cmdstr)
260- ensure_connected!(conn) do
305+ # Compute command
306+ magic = match_magic_syntax(cmdstr)
307+ if isnothing(magic)
308+ # Normal remote evaluation
309+ ex = parse_input(cmdstr)
310+
311+ ex = simple_macro_expand!(ex, Symbol(" @remote" )) do clientside_ex
312+ try
313+ x = Main. eval(clientside_ex)
314+ if x === Base. stdout
315+ # The local stdout cannot be serialized in any sensible way,
316+ # but we store a placeholder for it which will be transformed
317+ # into a serverside approximation of the client stream.
318+ return STDOUT_PLACEHOLDER
319+ else
320+ # Any expressions wrapped in `@remote` need to be executed
321+ # on the client and wrapped in a QuoteNode to prevent them
322+ # being eval'd again in the expression on the server side.
323+ QuoteNode(x)
324+ end
325+ catch _
326+ error(" Error while evaluating `@remote($clientside_ex )` before passing to the server" )
327+ end
328+ end
329+
330+ cmd = (:eval, ex)
331+ else
332+ # Magic prefixes
333+ if magic[1 ] == " ?"
334+ # Help mode
335+ cmd = (:help, magic[2 ])
336+ elseif magic[1 ] == " %module"
337+ mod_ex = Meta. parse(magic[2 ])
338+ cmd = (:in_module, mod_ex)
339+ elseif magic[1 ] == " %include"
340+ cmd = (:eval, parse_and_replace_includes(magic[2 ]))
341+ end
342+ end
343+
344+ messageid, value = ensure_connected!(conn) do
261345 # Set terminal properties for formatting result
262346 display_props = Dict(
263347 :displaysize=> displaysize(out_stream),
@@ -267,71 +351,23 @@ function run_remote_repl_command(conn, out_stream, cmdstr)
267351 # TODO breaking change - send these as part of :eval, perhaps ?
268352 send_and_receive(conn, (:display_properties, display_props), read_response= false )
269353
270- # Send actual command
271- magic = match_magic_syntax(cmdstr)
272- if isnothing(magic)
273- # Normal remote evaluation
274- ex = parse_input(cmdstr)
275-
276- ex = simple_macro_expand!(ex, Symbol(" @remote" )) do clientside_ex
277- try
278- x = Main. eval(clientside_ex)
279- if x === Base. stdout
280- # The local stdout cannot be serialized in any sensible way,
281- # but we store a placeholder for it which will be transformed
282- # into a serverside approximation of the client stream.
283- return STDOUT_PLACEHOLDER
284- else
285- # Any expressions wrapped in `@remote` need to be executed
286- # on the client and wrapped in a QuoteNode to prevent them
287- # being eval'd again in the expression on the server side.
288- QuoteNode(x)
289- end
290- catch _
291- error(" Error while evaluating `@remote($clientside_ex )` before passing to the server" )
292- end
293- end
354+ send_and_receive(conn, cmd)
355+ end
294356
295- cmd = (:eval, ex)
296- else
297- # Magic prefixes
298- if magic[1 ] == " ?"
299- # Help mode
300- cmd = (:help, magic[2 ])
301- elseif magic[1 ] == " %module"
302- mod_ex = Meta. parse(magic[2 ])
303- 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)))
357+ result_for_display = nothing
358+ if messageid in (:in_module, :eval_result, :help_result, :error)
359+ if ! isnothing(value)
360+ if messageid != :eval_result || ! REPL. ends_with_semicolon(cmdstr)
361+ result_for_display = Text(value)
317362 end
318363 end
319- messageid, value = send_and_receive(conn, cmd)
320- result_for_display = nothing
321- if messageid in (:in_module, :eval_result, :help_result, :error)
322- if ! isnothing(value)
323- if messageid != :eval_result || ! REPL. ends_with_semicolon(cmdstr)
324- result_for_display = Text(value)
325- end
326- end
327- if messageid == :in_module
328- conn. in_module = mod_ex
329- end
330- else
331- @error " Unexpected response from server" messageid
364+ if messageid == :in_module
365+ conn. in_module = mod_ex
332366 end
333- return result_for_display
367+ else
368+ @error " Unexpected response from server" messageid
334369 end
370+ return result_for_display
335371end
336372
337373remote_eval_and_fetch(:: Nothing , ex) = error(" No remote connection is active" )
0 commit comments