From d02c211e99add564515ec740bbd65d37cdb8f6f5 Mon Sep 17 00:00:00 2001 From: Michal Sojka Date: Sun, 5 Dec 2021 10:01:44 +0100 Subject: [PATCH 1/2] Don't run gnuplot process connected to stdout This change fixes incompatibility of Gnuplot.jl with Documenter.jl versions 0.27.0 and above. Without this change, Gnuplot.jl has at least these problems: 1. When building Gnuplot.jl documentation, the process blocks and never finishes. 2. When using Gnuplot.jl in docstrings in other code, running `doctest` blocks and never finishes. The reason is that Documenter uses a new version of IOCapture.jl, which contains this commit: https://github.com/JuliaDocs/IOCapture.jl/pull/9/commits/6cb4cdff34cd8f5d416bc4d0e36fe118e3f4d0bc. Documenter evaluates code snippets from the documentation with `stdout` redirected to a pipe to show the command's output. The mentioned commit changes the behavior so that now capturing waits until the pipe is closed. The problem with Gnuplot.jl is that when the gnuplot process is started as a part of the execution of documentation code snippet, its `stdout` is bound to Documenter's pipe. The pipe is not closed until the gnuplot process exits, which does not happen unless the code snippet calls `Gnuplot.quit` explicitly. Therefore Documenter blocks indefinitely. This can be demonstrated by storing the following code in `test.jl` module GnuplotDocTest """ ```jldoctest; setup = :(using Gnuplot) julia> @gp rand(100) ``` """ test() = nothing end using Documenter doctest(pwd(), [GnuplotDocTest]) and running `julia test.jl`. To fix this problem, we run the gnuplot process with stdout redirected to a pipe and create an asynchronous task, which reads the gnuplot's stdout and writes it to Julia's current stdout. Correctness of this approach can be verified by running: using Gnuplot Gnuplot.options.term = "dumb" @gp "plot sin(x)" Dumb terminal prints to stdout and the above command shows the graph on Julia's stdout too. In the next commit, we add the above code as a doctest. --- src/Gnuplot.jl | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Gnuplot.jl b/src/Gnuplot.jl index 18a8057..ffaa286 100644 --- a/src/Gnuplot.jl +++ b/src/Gnuplot.jl @@ -520,6 +520,18 @@ function readTask(gp::GPSession) delete!(sessions, gp.sid) end +# Read data from `from` and forward them to `stdout`. +# +# This is similar to `Base.write(to::IO, from::IO)` when called as +# write(stdout, from), but the difference is in situation when +# `stdout` changes. This function writes data to the changed `stdout`, +# whereas the call to `Base.write` writes to the original `stdout` +# forever. +function writeToStdout(from::IO) + while !eof(from) + write(stdout, readavailable(from)) + end +end function GPSession(sid::Symbol) session = DrySession(sid) @@ -535,13 +547,16 @@ function GPSession(sid::Symbol) end pin = Base.Pipe() + pout = Base.Pipe() perr = Base.Pipe() - proc = run(pipeline(`$(options.cmd)`, stdin=pin, stdout=stdout, stderr=perr), wait=false) + proc = run(pipeline(`$(options.cmd)`, stdin=pin, stdout=pout, stderr=perr), wait=false) chan = Channel{String}(32) # Close unused sides of the pipes + Base.close(pout.in) Base.close(perr.in) Base.close(pin.out) + Base.start_reading(pout.out) Base.start_reading(perr.out) out = GPSession(getfield.(Ref(session), fieldnames(DrySession))..., @@ -550,6 +565,7 @@ function GPSession(sid::Symbol) # Start reading tasks @async readTask(out) + @async writeToStdout(pout) # Read gnuplot default terminal if options.term == "" From 799154f53c32ef91b82662a4190785c525d57c9e Mon Sep 17 00:00:00 2001 From: Michal Sojka Date: Sun, 5 Dec 2021 11:47:34 +0100 Subject: [PATCH 2/2] Add doctest of dumb terminal This serves two purposes: 1. It demonstrates the functionality of the dumb terminal. 2. It checks that the previous commit works. --- docs/src/terminals.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/src/terminals.md b/docs/src/terminals.md index 875cd50..d7893e2 100644 --- a/docs/src/terminals.md +++ b/docs/src/terminals.md @@ -28,7 +28,31 @@ Press the `h` key on the window to display an help message with all available ke ## Plot in a terminal application (`dumb`, `sixel` and `sixelgd`) Gnuplot supports plotting in a terminal application, with no need for X11 or other GUI support, via the `dumb`, `sixel` and `sixelgd` terminals. These are extremely useful when you run Julia on a remote shell through `ssh`, with no X11 forwarding. -The `dumb` terminal uses ASCII characters to draw a plot, while `sixel` and `sixelgd` actually use bitmaps (but require Sixel support to be enabled in the terminal, e.g. `xterm -ti vt340`). A sixel plot on `xterm` looks as follows: +The `dumb` terminal uses ASCII characters to draw a plot, while `sixel` and `sixelgd` actually use bitmaps (but require Sixel support to be enabled in the terminal, e.g. `xterm -ti vt340`). Dumb terminal can be used as follows: + +```jldoctest; setup = :(using Gnuplot; origterm = Gnuplot.options.term) +julia> Gnuplot.options.term = "dumb size 60,15"; + +julia> @gp "plot sin(x)" + + 1 +-------------------------------------------------+ + 0.8 |-+ *+ * + ** ** + * * +-| + 0.6 |-+ * ** * * sin(x) *******-| + 0.4 |*+ * * * * * *+-| + 0.2 |*+ * * * * * *-| + 0 |*+ * * * * * *-| + | * * * * * * *| + -0.2 |-* * * * * * +*| + -0.4 |-+* * * * * * +*| + -0.6 |-+* * * * ** * +-| + -0.8 |-+ * * + ** ** + * * +-| + -1 +-------------------------------------------------+ + -10 -5 0 5 10 + +julia> Gnuplot.options.term = origterm; + +``` +A sixel plot on `xterm` looks as follows: ![](assets/sixelgd.png) The above terminals are available if gnuplot has been compiled with the `--with-bitmap-terminals` option enabled and Libgd (only for `sixelgd`).