From 70eca313c8f716902c9b48e4018bf77f932a848b Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Sun, 8 Oct 2017 12:17:00 +0200 Subject: [PATCH] IOContex: introduce the :typeinfo property Before, the :compact property was conflating 2 concepts: 1) are we low on screen space? 2) can we skip printing individual type information for elements in a collection? Cf. #22981 for context and discussion. Credit to Stefan Karpinski for the formulation of the design implemented here. --- base/arrayshow.jl | 422 ++++++++++++++++++++++++++++++++++++++++++++ base/grisu/grisu.jl | 8 +- base/precompile.jl | 6 +- base/replutil.jl | 4 +- base/set.jl | 11 +- base/show.jl | 352 ------------------------------------ base/sysimg.jl | 1 + examples/ModInts.jl | 2 +- 8 files changed, 436 insertions(+), 370 deletions(-) create mode 100644 base/arrayshow.jl diff --git a/base/arrayshow.jl b/base/arrayshow.jl new file mode 100644 index 0000000000000..cd26ffed91218 --- /dev/null +++ b/base/arrayshow.jl @@ -0,0 +1,422 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# methods related to array printing + +# Printing a value requires to take into account the :typeinfo property +# from the IO context; this property encodes (as a type) the type information +# that is supposed to have already been displayed concerning this value, +# so that redundancy can be avoided. For example, when printing an array of +# `Float16` values, the header "Float16" will be printed, and the values +# can simply be printed with the decimal representations: +# show(Float16(1)) -> "Float16(1.0)" +# show([Float16(1)]) -> "Float16[1.0]" (instead of "Float16[Float16(1)]") +# Similarly: +# show([[Float16(1)]]) -> "Array{Float16}[[1.0]]" (instead of "Array{Float16}[Float16[1.0]]") +# +# The array printing methods here can be grouped into two categories (and are annotated as such): +# 1) "typeinfo aware" : these are "API boundaries" functions, which will read the typeinfo +# property from the context, and pass down to their value an updated property +# according to its eltype; +# 2) "typeinfo agnostic": this are helper functions used by the first category; hence +# they don't manipulate the typeinfo property, and let the printing routines +# for their elements read directly the property set by their callers +# +# Non-annotated functions are even lower level (e.g. print_matrix_row), so they fall +# by default into category 2. +# +# The basic organization of this file is +# 1) printing with `display` +# 2) printing with `show` +# 3) Logic for displaying type information + + +## printing with `display` + +""" +Unexported convenience function used in body of `replace_in_print_matrix` +methods. By default returns a string of the same width as original with a +centered cdot, used in printing of structural zeros of structured matrices. +Accept keyword args `c` for alternate single character marker. +""" +function replace_with_centered_mark(s::AbstractString;c::Char = '⋅') + N = length(s) + return join(setindex!([" " for i=1:N],string(c),ceil(Int,N/2))) +end + +""" +`print_matrix_row(io, X, A, i, cols, sep)` produces the aligned output for +a single matrix row X[i, cols] where the desired list of columns is given. +The corresponding alignment A is used, and the separation between elements +is specified as string sep. +`print_matrix_row` will also respect compact output for elements. +""" +function print_matrix_row(io::IO, + X::AbstractVecOrMat, A::Vector, + i::Integer, cols::AbstractVector, sep::AbstractString) + isempty(A) || first(indices(cols,1)) == 1 || throw(DimensionMismatch("indices of cols ($(indices(cols,1))) must start at 1")) + for k = 1:length(A) + j = cols[k] + if isassigned(X,Int(i),Int(j)) # isassigned accepts only `Int` indices + x = X[i,j] + a = alignment(io, x) + sx = sprint(0, show, x, env=io) + else + a = undef_ref_alignment + sx = undef_ref_str + end + l = repeat(" ", A[k][1]-a[1]) # pad on left and right as needed + r = repeat(" ", A[k][2]-a[2]) + prettysx = replace_in_print_matrix(X,i,j,sx) + print(io, l, prettysx, r) + if k < length(A); print(io, sep); end + end +end + +""" +`print_matrix_vdots` is used to show a series of vertical ellipsis instead +of a bunch of rows for long matrices. Not only is the string vdots shown +but it also repeated every M elements if desired. +""" +function print_matrix_vdots(io::IO, vdots::AbstractString, + A::Vector, sep::AbstractString, M::Integer, m::Integer) + for k = 1:length(A) + w = A[k][1] + A[k][2] + if k % M == m + l = repeat(" ", max(0, A[k][1]-length(vdots))) + r = repeat(" ", max(0, w-length(vdots)-length(l))) + print(io, l, vdots, r) + else + print(io, repeat(" ", w)) + end + if k < length(A); print(io, sep); end + end +end + +# typeinfo agnostic +""" + print_matrix(io::IO, mat, pre, sep, post, hdots, vdots, ddots, hmod, vmod) + +Prints a matrix with limited output size. If `io` sets `:limit` to true, +then only the corners of the matrix are printed, separated with vertical, +horizontal, and diagonal ellipses as appropriate. +Optional arguments are string pre (printed before the matrix, e.g. an opening bracket) +which will cause a corresponding same-size indent on following rows, and +string post (printed at the end of the last row of the matrix). +Also options to use different ellipsis characters hdots, vdots, ddots. +These are repeated every hmod or vmod elements. +""" +function print_matrix(io::IO, X::AbstractVecOrMat, + pre::AbstractString = " ", # pre-matrix string + sep::AbstractString = " ", # separator between elements + post::AbstractString = "", # post-matrix string + hdots::AbstractString = " \u2026 ", + vdots::AbstractString = "\u22ee", + ddots::AbstractString = " \u22f1 ", + hmod::Integer = 5, vmod::Integer = 5) + + if !get(io, :limit, false) + screenheight = screenwidth = typemax(Int) + else + sz = displaysize(io) + screenheight, screenwidth = sz[1] - 4, sz[2] + end + screenwidth -= length(pre) + length(post) + presp = repeat(" ", length(pre)) # indent each row to match pre string + postsp = "" + @assert textwidth(hdots) == textwidth(ddots) + sepsize = length(sep) + rowsA, colsA = indices(X,1), indices(X,2) + m, n = length(rowsA), length(colsA) + # To figure out alignments, only need to look at as many rows as could + # fit down screen. If screen has at least as many rows as A, look at A. + # If not, then we only need to look at the first and last chunks of A, + # each half a screen height in size. + halfheight = div(screenheight,2) + if m > screenheight + rowsA = [rowsA[1:halfheight]; rowsA[m-div(screenheight-1,2)+1:m]] + end + # Similarly for columns, only necessary to get alignments for as many + # columns as could conceivably fit across the screen + maxpossiblecols = div(screenwidth, 1+sepsize) + if n > maxpossiblecols + colsA = [colsA[1:maxpossiblecols]; colsA[(n-maxpossiblecols+1):n]] + end + A = alignment(io, X, rowsA, colsA, screenwidth, screenwidth, sepsize) + # Nine-slicing is accomplished using print_matrix_row repeatedly + if m <= screenheight # rows fit vertically on screen + if n <= length(A) # rows and cols fit so just print whole matrix in one piece + for i in rowsA + print(io, i == first(rowsA) ? pre : presp) + print_matrix_row(io, X,A,i,colsA,sep) + print(io, i == last(rowsA) ? post : postsp) + if i != last(rowsA); println(io); end + end + else # rows fit down screen but cols don't, so need horizontal ellipsis + c = div(screenwidth-length(hdots)+1,2)+1 # what goes to right of ellipsis + Ralign = reverse(alignment(io, X, rowsA, reverse(colsA), c, c, sepsize)) # alignments for right + c = screenwidth - sum(map(sum,Ralign)) - (length(Ralign)-1)*sepsize - length(hdots) + Lalign = alignment(io, X, rowsA, colsA, c, c, sepsize) # alignments for left of ellipsis + for i in rowsA + print(io, i == first(rowsA) ? pre : presp) + print_matrix_row(io, X,Lalign,i,colsA[1:length(Lalign)],sep) + print(io, (i - first(rowsA)) % hmod == 0 ? hdots : repeat(" ", length(hdots))) + print_matrix_row(io, X, Ralign, i, (n - length(Ralign)) .+ colsA, sep) + print(io, i == last(rowsA) ? post : postsp) + if i != last(rowsA); println(io); end + end + end + else # rows don't fit so will need vertical ellipsis + if n <= length(A) # rows don't fit, cols do, so only vertical ellipsis + for i in rowsA + print(io, i == first(rowsA) ? pre : presp) + print_matrix_row(io, X,A,i,colsA,sep) + print(io, i == last(rowsA) ? post : postsp) + if i != rowsA[end] || i == rowsA[halfheight]; println(io); end + if i == rowsA[halfheight] + print(io, i == first(rowsA) ? pre : presp) + print_matrix_vdots(io, vdots,A,sep,vmod,1) + print(io, i == last(rowsA) ? post : postsp * '\n') + end + end + else # neither rows nor cols fit, so use all 3 kinds of dots + c = div(screenwidth-length(hdots)+1,2)+1 + Ralign = reverse(alignment(io, X, rowsA, reverse(colsA), c, c, sepsize)) + c = screenwidth - sum(map(sum,Ralign)) - (length(Ralign)-1)*sepsize - length(hdots) + Lalign = alignment(io, X, rowsA, colsA, c, c, sepsize) + r = mod((length(Ralign)-n+1),vmod) # where to put dots on right half + for i in rowsA + print(io, i == first(rowsA) ? pre : presp) + print_matrix_row(io, X,Lalign,i,colsA[1:length(Lalign)],sep) + print(io, (i - first(rowsA)) % hmod == 0 ? hdots : repeat(" ", length(hdots))) + print_matrix_row(io, X,Ralign,i,(n-length(Ralign)).+colsA,sep) + print(io, i == last(rowsA) ? post : postsp) + if i != rowsA[end] || i == rowsA[halfheight]; println(io); end + if i == rowsA[halfheight] + print(io, i == first(rowsA) ? pre : presp) + print_matrix_vdots(io, vdots,Lalign,sep,vmod,1) + print(io, ddots) + print_matrix_vdots(io, vdots,Ralign,sep,vmod,r) + print(io, i == last(rowsA) ? post : postsp * '\n') + end + end + end + if isempty(rowsA) + print(io, pre) + print(io, vdots) + length(colsA) > 1 && print(io, " ", ddots) + print(io, post) + end + end +end + +# typeinfo agnostic +# n-dimensional arrays +function show_nd(io::IO, a::AbstractArray, print_matrix::Function, label_slices::Bool) + limit::Bool = get(io, :limit, false) + if isempty(a) + return + end + tailinds = tail(tail(indices(a))) + nd = ndims(a)-2 + for I in CartesianRange(tailinds) + idxs = I.I + if limit + for i = 1:nd + ii = idxs[i] + ind = tailinds[i] + if length(ind) > 10 + if ii == ind[4] && all(d->idxs[d]==first(tailinds[d]),1:i-1) + for j=i+1:nd + szj = length(indices(a, j+2)) + indj = tailinds[j] + if szj>10 && first(indj)+2 < idxs[j] <= last(indj)-3 + @goto skip + end + end + #println(io, idxs) + print(io, "...\n\n") + @goto skip + end + if ind[3] < ii <= ind[end-3] + @goto skip + end + end + end + end + if label_slices + print(io, "[:, :, ") + for i = 1:(nd-1); print(io, "$(idxs[i]), "); end + println(io, idxs[end], "] =") + end + slice = view(a, indices(a,1), indices(a,2), idxs...) + print_matrix(io, slice) + print(io, idxs == map(last,tailinds) ? "" : "\n\n") + @label skip + end +end + +# print_array: main helper function for _display +# typeinfo agnostic + +# 0-dimensional arrays +print_array(io::IO, X::AbstractArray{T,0} where T) = isassigned(X) ? + show(io, X[]) : + print(io, undef_ref_str) + +print_array(io::IO, X::AbstractVecOrMat) = print_matrix(io, X) + +print_array(io::IO, X::AbstractArray) = show_nd(io, X, print_matrix, true) + +# typeinfo aware +# implements: show(io::IO, ::MIME"text/plain", X::AbstractArray) +function _display(io::IO, X::AbstractArray) + # 0) compute new IOContext + if !haskey(io, :compact) && length(indices(X, 2)) > 1 + io = IOContext(io, :compact => true) + end + if get(io, :limit, false) && eltype(X) === Method + # override usual show method for Vector{Method}: don't abbreviate long lists + io = IOContext(io, :limit => false) + end + # we assume this function is always called from top-level, i.e. that it's not nested + # within another "show" method; hence we always print the summary, without + # checking for current :typeinfo (this could be changed in the future) + io = IOContext(io, :typeinfo => eltype(X)) + + # 1) print summary info + print(io, summary(X)) + isempty(X) && return + print(io, ":") + if get(io, :limit, false) && displaysize(io)[1]-4 <= 0 + return print(io, " …") + else + println(io) + end + + # 2) show actual content + print_array(io, X) +end + + +## printing with `show` + +### non-Vector arrays + +# _show & _show_empty: main helper function for show(io, X) +# typeinfo agnostic + +""" +`_show(io, X::AbstractMatrix, prefix)` prints matrix X with opening and closing square brackets, +preceeded by `prefix`, supposed to encode the type of the elements. +""" +function _show(io::IO, X::AbstractMatrix, prefix::String) + @assert !isempty(X) + limit = get(io, :limit, false)::Bool + indr, indc = indices(X,1), indices(X,2) + nr, nc = length(indr), length(indc) + rdots, cdots = false, false + rr1, rr2 = UnitRange{Int}(indr), 1:0 + cr1, cr2 = UnitRange{Int}(indc), 1:0 + if limit + if nr > 4 + rr1, rr2 = rr1[1:2], rr1[nr-1:nr] + rdots = true + end + if nc > 4 + cr1, cr2 = cr1[1:2], cr1[nc-1:nc] + cdots = true + end + end + print(io, prefix, "[") + for rr in (rr1, rr2) + for i in rr + for cr in (cr1, cr2) + for j in cr + j > first(cr) && print(io, " ") + if !isassigned(X,i,j) + print(io, undef_ref_str) + else + el = X[i,j] + show(io, el) + end + end + if last(cr) == last(indc) + i < last(indr) && print(io, "; ") + elseif cdots + print(io, " \u2026 ") + end + end + end + last(rr) != nr && rdots && print(io, "\u2026 ; ") + end + print(io, "]") +end + + +_show(io::IO, X::AbstractArray, prefix::String) = + show_nd(io, X, (io, slice) -> _show(io, slice, prefix), false) + +# a specific call path is used to show vectors (show_vector) +_show(::IO, ::AbstractVector, ::String) = error("_show(::IO, ::AbstractVector, ::String) is not implemented") + +_show(io::IO, X::AbstractArray{T,0} where T, prefix::String) = print_array(io, X) + +# NOTE: it's not clear how this method could use the :typeinfo attribute +_show_empty(io::IO, X::Array{T}) where {T} = print(io, "Array{$T}(", join(size(X),','), ')') +_show_empty(io, X) = nothing # by default, we don't know this constructor + +# typeinfo aware (necessarily) +function show(io::IO, X::AbstractArray) + @assert ndims(X) != 1 + prefix = typeinfo_prefix(io, X) + io = IOContext(io, :typeinfo => eltype(X), :compact => true) + isempty(X) ? + _show_empty(io, X) : + _show(io, X, prefix) +end + +### Vector arrays + +# typeinfo aware +# NOTE: v is not constrained to be a vector, as this function can work with iterables +# in general (it's used e.g. by show(::IO, ::Set)) +function show_vector(io::IO, v, opn='[', cls=']') + print(io, typeinfo_prefix(io, v)) + # directly or indirectly, the context now knows about eltype(v): + io = IOContext(io, :typeinfo => eltype(v)) + io = IOContext(io, :compact => true) + limited = get(io, :limit, false) + if limited && _length(v) > 20 + inds = indices1(v) + show_delim_array(io, v, opn, ",", "", false, inds[1], inds[1]+9) + print(io, " \u2026 ") # i.e. " … " + show_delim_array(io, v, "", ",", cls, false, inds[end-9], inds[end]) + else + show_delim_array(io, v, opn, ",", cls, false) + end +end + +show(io::IO, X::AbstractVector) = show_vector(io, X) + + +## Logic for displaying type information + +# X not constrained, can be any iterable (cf. show_vector) +function typeinfo_prefix(io::IO, X) + typeinfo = typeinfo_context(io) + @assert X isa typeinfo + # what the context already knows about the eltype of X: + # (could use a more specific tool than eltype, maybe with _isleaftype) + eltype_ctx = eltype(typeinfo) + eltype_X = eltype(X) + # Types hard-coded here are those which are created by default for a given syntax + if eltype_X == eltype_ctx || !isempty(X) && eltype_X in (Float64, Int, Char, String) + "" + elseif print_without_params(eltype_X) + string(unwrap_unionall(eltype_X).name) # Print "Array" rather than "Array{T,N}" + else + string(eltype_X) + end +end + +typeinfo_context(io::IO) = get(io, :typeinfo, Any)::Union{DataType,UnionAll} diff --git a/base/grisu/grisu.jl b/base/grisu/grisu.jl index 64942d2775ba0..82e15cae89d86 100644 --- a/base/grisu/grisu.jl +++ b/base/grisu/grisu.jl @@ -118,15 +118,17 @@ function Base.show(io::IO, x::Union{Float64,Float32}) if get(io, :compact, false) _show(io, x, PRECISION, 6, x isa Float64, true) else - _show(io, x, SHORTEST, 0, true, false) + _show(io, x, SHORTEST, 0, Base.typeinfo_context(io) !== typeof(x), false) end end function Base.show(io::IO, x::Float16) - if get(io, :compact, false) + hastypeinfo = Base.typeinfo_context(io) === Float16 + # if hastypeinfo, the printing will be more compact using `SHORTEST` + if get(io, :compact, false) && !hastypeinfo _show(io, x, PRECISION, 5, false, true) else - _show(io, x, SHORTEST, 0, true, false) + _show(io, x, SHORTEST, 0, !hastypeinfo, false) end end diff --git a/base/precompile.jl b/base/precompile.jl index 2ccd832138828..05f6285491d8c 100644 --- a/base/precompile.jl +++ b/base/precompile.jl @@ -1602,7 +1602,6 @@ precompile(Tuple{typeof(Base.indexed_next), Tuple{Array{Int64, 1}, Void}, Int64, precompile(Tuple{typeof(Base.setdiff), Array{Int64, 1}, Array{Int64, 1}}) precompile(Tuple{typeof(Base.Multimedia.display), Array{Int64, 1}}) precompile(Tuple{typeof(Base.isassigned), Array{Int64, 1}, Int64}) -precompile(Tuple{typeof(Base.array_eltype_show_how), Array{Int64, 1}}) precompile(Tuple{typeof(Base.summary), Array{Int64, 1}, Tuple{Base.OneTo{Int64}}}) precompile(Tuple{typeof(Base.isassigned), Array{Int64, 1}, Int64, Int64}) precompile(Tuple{typeof(Base.isassigned), Array{Int64, 1}}) @@ -1614,9 +1613,8 @@ precompile(Tuple{typeof(Base.print), Base.IOContext{Base.Terminals.TTYTerminal}, precompile(Tuple{typeof(Base.print), Base.IOContext{Base.Terminals.TTYTerminal}, String, String}) precompile(Tuple{typeof(Base.print), Base.IOContext{Base.Terminals.TTYTerminal}, String, String, Char}) precompile(Tuple{typeof(Base.show_vector), Base.IOContext{Base.Terminals.TTYTerminal}, Array{Int64, 1}, String, String}) -precompile(Tuple{typeof(Base.print_matrix_repr), Base.IOContext{Base.Terminals.TTYTerminal}, Array{Int64, 1}}) -precompile(Tuple{typeof(Base.show_nd), Base.IOContext{Base.Terminals.TTYTerminal}, Array{Int64, 1}, typeof(Base.print_matrix_repr), Bool}) -precompile(Tuple{typeof(Base.repremptyarray), Base.IOContext{Base.Terminals.TTYTerminal}, Array{Int64, 1}}) +precompile(Tuple{typeof(Base._show), Base.IOContext{Base.Terminals.TTYTerminal}, Array{Int64, 1}, String}) +precompile(Tuple{typeof(Base._show_empty), Base.IOContext{Base.Terminals.TTYTerminal}, Array{Int64, 1}}) precompile(Tuple{typeof(Base.print_matrix), Base.IOContext{Base.Terminals.TTYTerminal}, Array{Int64, 1}, String, String, String}) precompile(Tuple{typeof(Base.getindex), Base.ImmutableDict{Symbol, Any}, Symbol}) precompile(Tuple{typeof(Base.vcat), Base.OneTo{Int64}}) diff --git a/base/replutil.jl b/base/replutil.jl index 7373367414e35..774697266f1af 100644 --- a/base/replutil.jl +++ b/base/replutil.jl @@ -136,7 +136,7 @@ function show(io::IO, ::MIME"text/plain", t::Task) end end -show(io::IO, ::MIME"text/plain", X::AbstractArray) = showarray(io, X, false) +show(io::IO, ::MIME"text/plain", X::AbstractArray) = _display(io, X) show(io::IO, ::MIME"text/plain", r::AbstractRange) = show(io, r) # always use the compact form for printing ranges # display something useful even for strings containing arbitrary @@ -146,7 +146,7 @@ function show(io::IO, ::MIME"text/plain", s::String) show(io, s) else println(io, sizeof(s), "-byte String of invalid UTF-8 data:") - showarray(io, Vector{UInt8}(s), false; header=false) + print_array(io, Vector{UInt8}(s)) end end diff --git a/base/set.jl b/base/set.jl index a50f1fbc187dc..8d0304f323653 100644 --- a/base/set.jl +++ b/base/set.jl @@ -28,14 +28,9 @@ similar(s::Set{T}) where {T} = Set{T}() similar(s::Set, T::Type) = Set{T}() function show(io::IO, s::Set) - print(io, "Set") - if isempty(s) - print(io, "{", eltype(s), "}()") - return - end - print(io, "(") - show_vector(io, s, "[", "]") - print(io, ")") + print(io, "Set(") + show_vector(io, s) + print(io, ')') end isempty(s::Set) = isempty(s.dict) diff --git a/base/show.jl b/base/show.jl index 1f6fd407b3a9f..1b17a1519d8f4 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1639,180 +1639,6 @@ function alignment(io::IO, X::AbstractVecOrMat, return a end -""" -Unexported convenience function used in body of `replace_in_print_matrix` -methods. By default returns a string of the same width as original with a -centered cdot, used in printing of structural zeros of structured matrices. -Accept keyword args `c` for alternate single character marker. -""" -function replace_with_centered_mark(s::AbstractString;c::Char = '⋅') - N = length(s) - return join(setindex!([" " for i=1:N],string(c),ceil(Int,N/2))) -end - -""" -`print_matrix_row(io, X, A, i, cols, sep)` produces the aligned output for -a single matrix row X[i, cols] where the desired list of columns is given. -The corresponding alignment A is used, and the separation between elements -is specified as string sep. -`print_matrix_row` will also respect compact output for elements. -""" -function print_matrix_row(io::IO, - X::AbstractVecOrMat, A::Vector, - i::Integer, cols::AbstractVector, sep::AbstractString) - isempty(A) || first(indices(cols,1)) == 1 || throw(DimensionMismatch("indices of cols ($(indices(cols,1))) must start at 1")) - for k = 1:length(A) - j = cols[k] - if isassigned(X,Int(i),Int(j)) # isassigned accepts only `Int` indices - x = X[i,j] - a = alignment(io, x) - sx = sprint(0, show, x, env=io) - else - a = undef_ref_alignment - sx = undef_ref_str - end - l = repeat(" ", A[k][1]-a[1]) # pad on left and right as needed - r = repeat(" ", A[k][2]-a[2]) - prettysx = replace_in_print_matrix(X,i,j,sx) - print(io, l, prettysx, r) - if k < length(A); print(io, sep); end - end -end - -""" -`print_matrix_vdots` is used to show a series of vertical ellipsis instead -of a bunch of rows for long matrices. Not only is the string vdots shown -but it also repeated every M elements if desired. -""" -function print_matrix_vdots(io::IO, vdots::AbstractString, - A::Vector, sep::AbstractString, M::Integer, m::Integer) - for k = 1:length(A) - w = A[k][1] + A[k][2] - if k % M == m - l = repeat(" ", max(0, A[k][1]-length(vdots))) - r = repeat(" ", max(0, w-length(vdots)-length(l))) - print(io, l, vdots, r) - else - print(io, repeat(" ", w)) - end - if k < length(A); print(io, sep); end - end -end - -""" - print_matrix(io::IO, mat, pre, sep, post, hdots, vdots, ddots, hmod, vmod) - -Prints a matrix with limited output size. If `io` sets `:limit` to true, -then only the corners of the matrix are printed, separated with vertical, -horizontal, and diagonal ellipses as appropriate. -Optional arguments are string pre (printed before the matrix, e.g. an opening bracket) -which will cause a corresponding same-size indent on following rows, and -string post (printed at the end of the last row of the matrix). -Also options to use different ellipsis characters hdots, vdots, ddots. -These are repeated every hmod or vmod elements. -""" -function print_matrix(io::IO, X::AbstractVecOrMat, - pre::AbstractString = " ", # pre-matrix string - sep::AbstractString = " ", # separator between elements - post::AbstractString = "", # post-matrix string - hdots::AbstractString = " \u2026 ", - vdots::AbstractString = "\u22ee", - ddots::AbstractString = " \u22f1 ", - hmod::Integer = 5, vmod::Integer = 5) - if !get(io, :limit, false) - screenheight = screenwidth = typemax(Int) - else - sz = displaysize(io) - screenheight, screenwidth = sz[1] - 4, sz[2] - end - screenwidth -= length(pre) + length(post) - presp = repeat(" ", length(pre)) # indent each row to match pre string - postsp = "" - @assert textwidth(hdots) == textwidth(ddots) - sepsize = length(sep) - rowsA, colsA = indices(X,1), indices(X,2) - m, n = length(rowsA), length(colsA) - # To figure out alignments, only need to look at as many rows as could - # fit down screen. If screen has at least as many rows as A, look at A. - # If not, then we only need to look at the first and last chunks of A, - # each half a screen height in size. - halfheight = div(screenheight,2) - if m > screenheight - rowsA = [rowsA[1:halfheight]; rowsA[m-div(screenheight-1,2)+1:m]] - end - # Similarly for columns, only necessary to get alignments for as many - # columns as could conceivably fit across the screen - maxpossiblecols = div(screenwidth, 1+sepsize) - if n > maxpossiblecols - colsA = [colsA[1:maxpossiblecols]; colsA[(n-maxpossiblecols+1):n]] - end - A = alignment(io, X, rowsA, colsA, screenwidth, screenwidth, sepsize) - # Nine-slicing is accomplished using print_matrix_row repeatedly - if m <= screenheight # rows fit vertically on screen - if n <= length(A) # rows and cols fit so just print whole matrix in one piece - for i in rowsA - print(io, i == first(rowsA) ? pre : presp) - print_matrix_row(io, X,A,i,colsA,sep) - print(io, i == last(rowsA) ? post : postsp) - if i != last(rowsA); println(io); end - end - else # rows fit down screen but cols don't, so need horizontal ellipsis - c = div(screenwidth-length(hdots)+1,2)+1 # what goes to right of ellipsis - Ralign = reverse(alignment(io, X, rowsA, reverse(colsA), c, c, sepsize)) # alignments for right - c = screenwidth - sum(map(sum,Ralign)) - (length(Ralign)-1)*sepsize - length(hdots) - Lalign = alignment(io, X, rowsA, colsA, c, c, sepsize) # alignments for left of ellipsis - for i in rowsA - print(io, i == first(rowsA) ? pre : presp) - print_matrix_row(io, X,Lalign,i,colsA[1:length(Lalign)],sep) - print(io, (i - first(rowsA)) % hmod == 0 ? hdots : repeat(" ", length(hdots))) - print_matrix_row(io, X, Ralign, i, (n - length(Ralign)) .+ colsA, sep) - print(io, i == last(rowsA) ? post : postsp) - if i != last(rowsA); println(io); end - end - end - else # rows don't fit so will need vertical ellipsis - if n <= length(A) # rows don't fit, cols do, so only vertical ellipsis - for i in rowsA - print(io, i == first(rowsA) ? pre : presp) - print_matrix_row(io, X,A,i,colsA,sep) - print(io, i == last(rowsA) ? post : postsp) - if i != rowsA[end] || i == rowsA[halfheight]; println(io); end - if i == rowsA[halfheight] - print(io, i == first(rowsA) ? pre : presp) - print_matrix_vdots(io, vdots,A,sep,vmod,1) - print(io, i == last(rowsA) ? post : postsp * '\n') - end - end - else # neither rows nor cols fit, so use all 3 kinds of dots - c = div(screenwidth-length(hdots)+1,2)+1 - Ralign = reverse(alignment(io, X, rowsA, reverse(colsA), c, c, sepsize)) - c = screenwidth - sum(map(sum,Ralign)) - (length(Ralign)-1)*sepsize - length(hdots) - Lalign = alignment(io, X, rowsA, colsA, c, c, sepsize) - r = mod((length(Ralign)-n+1),vmod) # where to put dots on right half - for i in rowsA - print(io, i == first(rowsA) ? pre : presp) - print_matrix_row(io, X,Lalign,i,colsA[1:length(Lalign)],sep) - print(io, (i - first(rowsA)) % hmod == 0 ? hdots : repeat(" ", length(hdots))) - print_matrix_row(io, X,Ralign,i,(n-length(Ralign)).+colsA,sep) - print(io, i == last(rowsA) ? post : postsp) - if i != rowsA[end] || i == rowsA[halfheight]; println(io); end - if i == rowsA[halfheight] - print(io, i == first(rowsA) ? pre : presp) - print_matrix_vdots(io, vdots,Lalign,sep,vmod,1) - print(io, ddots) - print_matrix_vdots(io, vdots,Ralign,sep,vmod,r) - print(io, i == last(rowsA) ? post : postsp * '\n') - end - end - end - if isempty(rowsA) - print(io, pre) - print(io, vdots) - length(colsA) > 1 && print(io, " ", ddots) - print(io, post) - end - end -end """ summary(io::IO, x) @@ -1940,154 +1766,6 @@ function showarg(io::IO, r::ReinterpretArray{T}, toplevel) where {T} print(io, ')') end -# n-dimensional arrays -function show_nd(io::IO, a::AbstractArray, print_matrix, label_slices) - limit::Bool = get(io, :limit, false) - if isempty(a) - return - end - tailinds = tail(tail(indices(a))) - nd = ndims(a)-2 - for I in CartesianRange(tailinds) - idxs = I.I - if limit - for i = 1:nd - ii = idxs[i] - ind = tailinds[i] - if length(ind) > 10 - if ii == ind[4] && all(d->idxs[d]==first(tailinds[d]),1:i-1) - for j=i+1:nd - szj = length(indices(a, j+2)) - indj = tailinds[j] - if szj>10 && first(indj)+2 < idxs[j] <= last(indj)-3 - @goto skip - end - end - #println(io, idxs) - print(io, "...\n\n") - @goto skip - end - if ind[3] < ii <= ind[end-3] - @goto skip - end - end - end - end - if label_slices - print(io, "[:, :, ") - for i = 1:(nd-1); print(io, "$(idxs[i]), "); end - println(io, idxs[end], "] =") - end - slice = view(a, indices(a,1), indices(a,2), idxs...) - print_matrix(io, slice) - print(io, idxs == map(last,tailinds) ? "" : "\n\n") - @label skip - end -end - -""" -`print_matrix_repr(io, X)` prints matrix X with opening and closing square brackets. -""" -function print_matrix_repr(io, X::AbstractArray) - limit = get(io, :limit, false)::Bool - compact, prefix = array_eltype_show_how(X) - if compact && !haskey(io, :compact) - io = IOContext(io, :compact => compact) - end - indr, indc = indices(X,1), indices(X,2) - nr, nc = length(indr), length(indc) - rdots, cdots = false, false - rr1, rr2 = UnitRange{Int}(indr), 1:0 - cr1, cr2 = UnitRange{Int}(indc), 1:0 - if limit - if nr > 4 - rr1, rr2 = rr1[1:2], rr1[nr-1:nr] - rdots = true - end - if nc > 4 - cr1, cr2 = cr1[1:2], cr1[nc-1:nc] - cdots = true - end - end - print(io, prefix, "[") - for rr in (rr1, rr2) - for i in rr - for cr in (cr1, cr2) - for j in cr - j > first(cr) && print(io, " ") - if !isassigned(X,i,j) - print(io, undef_ref_str) - else - el = X[i,j] - show(io, el) - end - end - if last(cr) == last(indc) - i < last(indr) && print(io, "; ") - elseif cdots - print(io, " \u2026 ") - end - end - end - last(rr) != nr && rdots && print(io, "\u2026 ; ") - end - print(io, "]") -end - -show(io::IO, X::AbstractArray) = showarray(io, X, true) - -repremptyarray(io::IO, X::Array{T}) where {T} = print(io, "Array{$T}(", join(size(X),','), ')') -repremptyarray(io, X) = nothing # by default, we don't know this constructor - -function showarray(io::IO, X::AbstractArray, repr::Bool = true; header = true) - if repr && ndims(X) == 1 - return show_vector(io, X, "[", "]") - end - if !haskey(io, :compact) && length(indices(X, 2)) > 1 - io = IOContext(io, :compact => true) - end - if !repr && get(io, :limit, false) && eltype(X) === Method - # override usual show method for Vector{Method}: don't abbreviate long lists - io = IOContext(io, :limit => false) - end - (!repr && header) && print(io, summary(X)) - if !isempty(X) - if !repr && header - print(io, ":") - if get(io, :limit, false) && displaysize(io)[1]-4 <= 0 - return print(io, " …") - else - println(io) - end - end - if ndims(X) == 0 - if isassigned(X) - return show(io, X[]) - else - return print(io, undef_ref_str) - end - end - if repr - if ndims(X) <= 2 - print_matrix_repr(io, X) - else - show_nd(io, X, print_matrix_repr, false) - end - else - punct = (" ", " ", "") - if ndims(X) <= 2 - print_matrix(io, X, punct...) - else - show_nd(io, X, - (io, slice) -> print_matrix(io, slice, punct...), - !repr) - end - end - elseif repr - repremptyarray(io, X) - end -end - """ showcompact(x) showcompact(io::IO, x) @@ -2111,36 +1789,6 @@ function showcompact(io::IO, x) end end -# returns compact, prefix -function array_eltype_show_how(X) - e = eltype(X) - if print_without_params(e) - str = string(unwrap_unionall(e).name) # Print "Array" rather than "Array{T,N}" - else - str = string(e) - end - # Types hard-coded here are those which are created by default for a given syntax - (_isleaftype(e), - (!isempty(X) && (e===Float64 || e===Int || e===Char || e===String) ? "" : str)) -end - -function show_vector(io::IO, v, opn, cls) - compact, prefix = array_eltype_show_how(v) - limited = get(io, :limit, false) - if compact && !haskey(io, :compact) - io = IOContext(io, :compact => compact) - end - print(io, prefix) - if limited && _length(v) > 20 - inds = indices1(v) - show_delim_array(io, v, opn, ",", "", false, inds[1], inds[1]+9) - print(io, " \u2026 ") - show_delim_array(io, v, "", ",", cls, false, inds[end-9], inds[end]) - else - show_delim_array(io, v, opn, ",", cls, false) - end -end - # printing BitArrays # (following functions not exported - mainly intended for debug) diff --git a/base/sysimg.jl b/base/sysimg.jl index 0a3963d4872a2..5e99703f9b092 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -224,6 +224,7 @@ include("parse.jl") include("shell.jl") include("regex.jl") include("show.jl") +include("arrayshow.jl") # multidimensional arrays include("cartesian.jl") diff --git a/examples/ModInts.jl b/examples/ModInts.jl index 59fde002d26c5..0354d2e8bd830 100644 --- a/examples/ModInts.jl +++ b/examples/ModInts.jl @@ -11,7 +11,7 @@ struct ModInt{n} <: Integer end Base.show(io::IO, k::ModInt{n}) where {n} = - print(io, get(io, :compact, false) ? k.k : "$(k.k) mod $n") + print(io, Base.typeinfo_context(io) == typeof(k) ? k.k : "$(k.k) mod $n") (+)(a::ModInt{n}, b::ModInt{n}) where {n} = ModInt{n}(a.k+b.k) (-)(a::ModInt{n}, b::ModInt{n}) where {n} = ModInt{n}(a.k-b.k)