Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix display(d, m, x) where m expects a json string #756

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 121 additions & 37 deletions src/execute_request.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,136 @@
import Base.Libc: flush_cstdio
import Pkg

const text_plain = MIME("text/plain")
const image_svg = MIME("image/svg+xml")
const image_png = MIME("image/png")
const image_jpeg = MIME("image/jpeg")
const text_markdown = MIME("text/markdown")
const text_html = MIME("text/html")
const text_latex = MIME("text/latex") # Jupyter expects this
const text_latex2 = MIME("application/x-latex") # but this is more standard?
const application_vnd_vega_v3 = MIME("application/vnd.vega.v3+json")
const application_vnd_vegalite_v2 = MIME("application/vnd.vegalite.v2+json")
const application_vnd_dataresource = MIME("application/vnd.dataresource+json")
Base.showable(a::Vector{<:MIME}, x) = any(m -> showable(m, x), a)

"""
A vector of MIME types (or vectors of MIME types) that IJulia will try to
render. IJulia will try to render every MIME type specified in the first level
of the vector. If a vector of MIME types is specified, IJulia will include only
the first MIME type that is renderable (this allows for the expression of
priority and exclusion of redundant data).

For example, since "text/plain" is specified as a first-child of the array,
IJulia will always try to include a "text/plain" representation of anything that
is displayed. Since markdown and latex are specified within a sub-vector, IJulia
will always try to render "text/markdown", and will only try to render
"text/latex" if markdown isn't possible.
"""
const ijulia_mime_types = Vector{Union{MIME, Vector{<:MIME}}}()

function register_mime(x::Union{M, Vector{M}}) where {M <: MIME}
push!(ijulia_mime_types, x)
if x isa MIME
register_mime_display(x)
else
register_mime_display.(x)
end
end

import Base: display, redisplay
struct InlineDisplay <: AbstractDisplay end
function register_mime_display(mime)
mime_type = typeof(mime)
mime_str = string(mime)
@eval begin
function display(d::InlineDisplay, m::$(mime_type), x)
send_ipython(publish[],
msg_pub(execute_msg, "display_data",
Dict(
"metadata" => metadata(x), # optional
"data" => Dict($(mime_str)=>display_mimestring(m, x)[2]))))
end
displayable(d::InlineDisplay, ::MIME{Symbol($mime)}) = true
end
end

register_mime.([MIME("text/plain"),
MIME("image/svg+xml"),
[MIME("image/png"),MIME("image/jpeg")],
[MIME("text/markdown"),
MIME("text/html"),
MIME("text/latex"), # Jupyter expects this
MIME("application/x-latex")], # but this is more standard?
[MIME("text/javascript"), MIME("application/javascript")],
])

include("magics.jl")

# return a String=>Any dictionary to attach as metadata
# in Jupyter display_data and pyout messages
metadata(x) = Dict()

# return a String=>String dictionary of mimetype=>data
# for passing to Jupyter display_data and execute_result messages.
function display_dict(x)
data = Dict{String,Any}("text/plain" => limitstringmime(text_plain, x))
if showable(application_vnd_vegalite_v2, x)
data[string(application_vnd_vegalite_v2)] = JSON.JSONText(limitstringmime(application_vnd_vegalite_v2, x))
elseif showable(application_vnd_vega_v3, x) # don't send vega if we have vega-lite
data[string(application_vnd_vega_v3)] = JSON.JSONText(limitstringmime(application_vnd_vega_v3, x))
end
if showable(application_vnd_dataresource, x)
data[string(application_vnd_dataresource)] = JSON.JSONText(limitstringmime(application_vnd_dataresource, x))
end
if showable(image_svg, x)
data[string(image_svg)] = limitstringmime(image_svg, x)
"""
Generate the preferred MIME representation of x.

Returns a tuple with the selected MIME type and the representation of the data
using that MIME type.
"""
function display_mimestring end

abstract type MIMEStringType end
struct RawMIMEString <: MIMEStringType end
mimestringtype(m::MIME) = RawMIMEString
display_mimestring(::Type{T}, m::MIME, x) where{T<:MIMEStringType} = display_mimestring(T(), m, x)
display_mimestring(::RawMIMEString, m::MIME, x) = (m, limitstringmime(m, x))
display_mimestring(m::MIME, x) = display_mimestring(mimestringtype(m), m, x)
function display_mimestring(mime_array::Vector{<:MIME}, x)
for m in mime_array
if showable(m, x)
return display_mimestring(m, x)
end
end
if showable(image_png, x)
data[string(image_png)] = limitstringmime(image_png, x)
elseif showable(image_jpeg, x) # don't send jpeg if we have png
data[string(image_jpeg)] = limitstringmime(image_jpeg, x)
error("No displayable MIME types in mime array.")
end

"""
If false then data of the mime type should be sent to ipython in b64-encoded string.
If true then it will be sent as is.
Defaults to the value of istextmime.
Use a private name to avoid type piracy.
"""
_istextmime(m::MIME) = istextmime(m)

"""
To add a new MIME type that require special treatment before sending to ipython,
follow the example of JSONMIMEString to do the following:
0. define a singleton type inherited from MIMEStringType. This is a trait type.
1. specialize mimestringtype on the new MIME returning the new trait.
2. (optinal) specialize _istextmime to return true if the new MIME should be send as text.
3. specialize display_mimestring to implement your special treatment.
4. register_mime
"""
struct JSONMIMEString <: MIMEStringType end
for mime in [[MIME("application/vnd.vegalite.v2+json"), MIME("application/vnd.vega.v3+json")],
MIME("application/vnd.vega.v4+json"),
MIME("application/vnd.dataresource+json")]
m = typeof(mime)
if m <: MIME
@eval mimestringtype(::$m) = JSONMIMEString
@eval _istextmime(::$m) = true
else
for _m in mime
m = typeof(_m)
@eval mimestringtype(::$m) = JSONMIMEString
@eval _istextmime(::$m) = true
end
end
if showable(text_markdown, x)
data[string(text_markdown)] = limitstringmime(text_markdown, x)
elseif showable(text_html, x)
data[string(text_html)] = limitstringmime(text_html, x)
elseif showable(text_latex, x)
data[string(text_latex)] = limitstringmime(text_latex, x)
elseif showable(text_latex2, x)
data[string(text_latex)] = limitstringmime(text_latex2, x)
register_mime(mime)
end
display_mimestring(::JSONMIMEString, m::MIME, x) = (m, JSON.JSONText(limitstringmime(m, x)))

"""
Generate a dictionary of `mime_type => data` pairs for all registered MIME
types. This is the format that Jupyter expects in display_data and
execute_result messages.
"""
function display_dict(x)
data = Dict{String, Union{String, JSONText}}()
for m in ijulia_mime_types
if showable(m, x)
mime, mime_repr = display_mimestring(m, x)
data[string(mime)] = mime_repr
end
end
return data
end
Expand Down
44 changes: 3 additions & 41 deletions src/inline.jl
Original file line number Diff line number Diff line change
@@ -1,22 +1,3 @@
import Base: display, redisplay

struct InlineDisplay <: AbstractDisplay end

# supported MIME types for inline display in IPython, in descending order
# of preference (descending "richness")
const ipy_mime = [
"application/vnd.dataresource+json",
"application/vnd.vegalite.v2+json",
"application/vnd.vega.v3+json",
"text/html",
"text/latex",
"image/svg+xml",
"image/png",
"image/jpeg",
"text/plain",
"text/markdown",
"application/javascript"
]

# need special handling for showing a string as a textmime
# type, since in that case the string is assumed to be
Expand All @@ -29,7 +10,7 @@ israwtext(::MIME, x) = false
# IOContext that tells the underlying show function to limit output
function limitstringmime(mime::MIME, x)
buf = IOBuffer()
if istextmime(mime)
if _istextmime(mime)
if israwtext(mime, x)
return String(x)
else
Expand All @@ -47,32 +28,13 @@ function limitstringmime(mime::MIME, x)
return String(take!(buf))
end

for mime in ipy_mime
@eval begin
function display(d::InlineDisplay, ::MIME{Symbol($mime)}, x)
send_ipython(publish[],
msg_pub(execute_msg, "display_data",
Dict(
"metadata" => metadata(x), # optional
"data" => Dict($mime => limitstringmime(MIME($mime), x)))))
end
displayable(d::InlineDisplay, ::MIME{Symbol($mime)}) = true
end
end

# deal with annoying application/x-latex == text/latex synonyms
display(d::InlineDisplay, m::MIME"application/x-latex", x) = display(d, MIME("text/latex"), limitstringmime(m, x))

# deal with annoying text/javascript == application/javascript synonyms
display(d::InlineDisplay, m::MIME"text/javascript", x) = display(d, MIME("application/javascript"), limitstringmime(m, x))

# If the user explicitly calls display("foo/bar", x), we send
# the display message, also sending text/plain for text data.
displayable(d::InlineDisplay, M::MIME) = istextmime(M)
displayable(d::InlineDisplay, M::MIME) = _istextmime(M)
function display(d::InlineDisplay, M::MIME, x)
sx = limitstringmime(M, x)
d = Dict(string(M) => sx)
if istextmime(M)
if _istextmime(M)
d["text/plain"] = sx # directly show text data, e.g. text/csv
end
send_ipython(publish[],
Expand Down