Nested indentation in show

When writing some methods for show for types in a package, I was wondering if there is any mechanism (or package) for reusing show methods for fields but nesting the indentation.

Eg suppose a type prints as

This is a Foo with 3x2 elements.
    [1.0 1.0 1.0; 1.0 1.0 1.0; 1.0 1.0 1.0]

and a type Bar prints as

This is a Bar with critical value 0.7

Then for a type Baz

struct  Bar
    foo::Foo 
    bar::Bar 
end

is there an organized way to write a show method that

  1. reuses show code for the fields,
  2. indents the fields.

Eg

This is a Bar with fields
    This is a Foo with 3x2 elements.
        [1.0 1.0 1.0; 1.0 1.0 1.0; 1.0 1.0 1.0]
    This is a Bar with critical value 0.7    

Solutions I have thought of:

  1. use something other than indentation, eg ,
  2. pass around the current indentation as an optional 3rd argument of show, defaulting to 0.

Neither of these is very clean.

2 Likes

I’m not aware of a generic solution for indentation, but the official way to pass arguments to show is to use IOContext. Eg. show(IOContext(indent=get(io, :indent, 0)+2), mime, child_object). See also this example for displaying a tree. It works, but I do dearly miss CL’s special variables.

5 Likes

When I’ve need to do this in the past I’ve added an indent field to
[IOContext]
(https://docs.julialang.org/en/stable/stdlib/io-network/#Base.IOContext)
that gets added to whenever we need to nest another level. It won’t work
with Base types or types from other packages, but within a package you
can use it for all your types that could be nested within each other.
-s

2 Likes

updated link
https://docs.julialang.org/en/v1/base/io-network/#Base.IOContext

1 Like

If you want a kludgey half-solution that works even for types you don’t own, you can have your types make and pass down an IOBuffer instead of the io arg when they recurse into show on their children, then re-emit each line of that buffer with indentation added to the front. It looks sort of like this:

function Base.show(io::IO, mime, x::Bar)
    println(io, "This is a Bar with fields")
    buffer = IOBuffer()
    for c in x.children
        show(buffer, mime, c)
        for line in split(String(take!(buffer)), "\n")
            println(io, "    ", line)
        end
    end
end

Other types won’t be able to add new levels of indentation, so e.g. Dicts might be a touch ugly, but at least now even multiline children will be properly indented inside your own types.

(Yes, the performance is horrible, almost certainly outright quadratic in the depth unless IOBuffer and String and take! are all doing some deep cooperative pointer-shuffling magic, but it’s pretty-printing for human inspection so N is like eight at most, lol. :stuck_out_tongue: )