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

New range display2 #13534

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
66 changes: 66 additions & 0 deletions base/range.jl
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,72 @@ function show(io::IO, r::LinSpace)
print(io, ')')
end

"""
`print_range(io, r)` prints out a nice looking range r in terms of its elements
as if it were `collect(r)`, dependent on the size of the
terminal, and taking into account whether compact numbers should be shown.
It figures out the width in characters of each element, and if they
end up too wide, it shows the first and last elements separated by a
horizontal elipsis. Typical output will look like `1.0,2.0,3.0,…,4.0,5.0,6.0`.

`print_range(io, r, sz, pre, sep, post, hdots)` uses optional
parameters `sz` for the (rows,cols) of the screen,
`pre` and `post` characters for each printed row, `sep` separator string between
printed elements, `hdots` string for the horizontal ellipsis.
"""
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring is duplicated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't say duplicated, it's just that I'm not a huge fan of f(a, [b, c]) to show optional parameters. I think it's easier for a reader to parse the simpler call first, and then see how additional parameters are added on. If the square brackets are considered the standard, I can switch to that, but otherwise I prefer my docstring.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look again. You have the whole docstring twice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doh! sorry about that; removed.

`print_range(io, r)` prints out a nice looking range r in terms of its elements
as if it were `collect(r)`, dependent on the size of the
terminal, and taking into account whether compact numbers should be shown.
It figures out the width in characters of each element, and if they
end up too wide, it shows the first and last elements separated by a
horizontal elipsis. Typical output will look like `1.0,2.0,3.0,…,4.0,5.0,6.0`.

`print_range(io, r, sz, pre, sep, post, hdots)` uses optional
parameters `sz` for the (rows,cols) of the screen,
`pre` and `post` characters for each printed row, `sep` separator string between
printed elements, `hdots` string for the horizontal ellipsis.
"""
function print_range(io::IO, r::Range,
sz::Tuple{Integer, Integer} = (s = tty_size(); (s[1]-4, s[2])),
pre::AbstractString = " ",
sep::AbstractString = ",",
post::AbstractString = "",
hdots::AbstractString = ",\u2026,") # horiz ellipsis
# This function borrows from print_matrix() in show.jl
# and should be called by writemime (replutil.jl) and by display()
screenheight, screenwidth = sz
screenwidth -= length(pre) + length(post)
postsp = ""
sepsize = length(sep)
m = 1 # treat the range as a one-row matrix
n = length(r)
# Figure out spacing alignments for r, but only need to examine the
# left and right edge columns, as many as could conceivably fit on the
# screen, with the middle columns summarized by ellipsis
maxpossiblecols = div(screenwidth, 1+sepsize) # assume each element is at least 1 char + 1 separator
colsr = n <= maxpossiblecols ? (1:n) : [1:div(maxpossiblecols,2)+1; (n-div(maxpossiblecols,2)):n]
rowmatrix = r[colsr]' # treat the range as a one-row matrix for print_matrix_row
A = alignment(rowmatrix,1:m,1:length(rowmatrix),screenwidth,screenwidth,sepsize) # how much space range takes
if n <= length(A) # cols fit screen, so print out all elements
print(io, pre) # put in pre chars
print_matrix_row(io,rowmatrix,A,1,1:n,sep) # the entire range
print(io, post) # add the post characters
else # cols don't fit so put horiz ellipsis in the middle
# how many chars left after dividing width of screen in half
# and accounting for the horiz ellipsis
c = div(screenwidth-length(hdots)+1,2)+1 # chars remaining for each side of rowmatrix
alignR = reverse(alignment(rowmatrix,1:m,length(rowmatrix):-1:1,c,c,sepsize)) # which cols of rowmatrix to put on the right
c = screenwidth - sum(map(sum,alignR)) - (length(alignR)-1)*sepsize - length(hdots)
alignL = alignment(rowmatrix,1:m,1:length(rowmatrix),c,c,sepsize) # which cols of rowmatrix to put on the left
print(io, pre) # put in pre chars
print_matrix_row(io, rowmatrix,alignL,1,1:length(alignL),sep) # left part of range
print(io, hdots) # horizontal ellipsis
print_matrix_row(io, rowmatrix,alignR,1,length(rowmatrix)-length(alignR)+1:length(rowmatrix),sep) # right part of range
print(io, post) # post chars
end
end

logspace(start::Real, stop::Real, n::Integer=50) = 10.^linspace(start, stop, n)

## interface implementations
Expand Down
25 changes: 17 additions & 8 deletions base/replutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,24 @@ function writemime(io::IO, ::MIME"text/plain", f::Function)
end
end

# writemime for ranges, e.g.
# 3-element UnitRange{Int64,Int}
# 1,2,3
# or for more elements than fit on screen:
# 1.0,2.0,3.0,…,6.0,7.0,8.0
function writemime(io::IO, ::MIME"text/plain", r::Union{Range, LinSpace})
print(io, summary(r))
if !isempty(r)
println(io, ":")
with_output_limit(()->print_range(io, r))
end
end

function writemime(io::IO, ::MIME"text/plain", v::AbstractVector)
if isa(v, Range)
show(io, v)
else
print(io, summary(v))
if !isempty(v)
println(io, ":")
with_output_limit(()->print_matrix(io, v))
end
print(io, summary(v))
if !isempty(v)
println(io, ":")
with_output_limit(()->print_matrix(io, v))
end
end

Expand Down
85 changes: 67 additions & 18 deletions base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -956,14 +956,21 @@ dump(io::IO, x::DataType) = dump(io, x, 5, "")
dump(io::IO, x::TypeVar, n::Int, indent) = println(io, x.name)


"""
`alignment(X)` returns a tuple (left,right) showing how many characters are
needed on either side of an alignment feature such as a decimal point.
"""
alignment(x::Any) = (0, length(sprint(showcompact_lim, x)))
alignment(x::Number) = (length(sprint(showcompact_lim, x)), 0)
"`alignment(42)` yields (2,0)"
alignment(x::Integer) = (length(sprint(showcompact_lim, x)), 0)
"`alignment(4.23)` yields (1,3) for `4` and `.23`"
function alignment(x::Real)
m = match(r"^(.*?)((?:[\.eE].*)?)$", sprint(showcompact_lim, x))
m === nothing ? (length(sprint(showcompact_lim, x)), 0) :
(length(m.captures[1]), length(m.captures[2]))
end
"`alignment(1 + 10im)` yields (3,5) for `1 +` and `_10im` (plus sign on left, space on right)"
function alignment(x::Complex)
m = match(r"^(.*[\+\-])(.*)$", sprint(showcompact_lim, x))
m === nothing ? (length(sprint(showcompact_lim, x)), 0) :
Expand All @@ -978,26 +985,37 @@ end
const undef_ref_str = "#undef"
const undef_ref_alignment = (3,3)

"""
`alignment(X, rows, cols, cols_if_complete, cols_otherwise, sep)` returns the
alignment for specified parts of array `X`, returning the (left,right) info.
It will look in X's `rows`, `cols` (both lists of indices)
and figure out what's needed to be fully aligned, for example looking all
the way down a column and finding out the maximum size of each element.
Parameter `sep::Integer` is number of spaces to put between elements.
`cols_if_complete` and `cols_otherwise` indicate screen width to use.
Alignment is reported as a vector of (left,right) tuples, one for each
column going across the screen.
"""
function alignment(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The additional documentation is great, but you might as well put it in the form of a docstring:

"""
    alignment(X, rows, cols, cols_if_complete, cols_otherwise, sep)

Returns the alignment for the specified parts of array `X`, returning ....
...
"""
function alignment( ...

X::AbstractVecOrMat,
rows::AbstractVector, cols::AbstractVector,
cols_if_complete::Integer, cols_otherwise::Integer, sep::Integer
)
a = Tuple{Int, Int}[]
for j in cols
for j in cols # need to go down each column one at a time
l = r = 0
for i in rows
for i in rows # plumb down and see what largest element sizes are
if isassigned(X,i,j)
aij = alignment(X[i,j])
else
aij = undef_ref_alignment
end
l = max(l, aij[1])
r = max(r, aij[2])
l = max(l, aij[1]) # left characters
r = max(r, aij[2]) # right characters
end
push!(a, (l, r))
push!(a, (l, r)) # one tuple per column of X, pruned to screen width
if length(a) > 1 && sum(map(sum,a)) + sep*length(a) >= cols_if_complete
pop!(a)
pop!(a) # remove this latest tuple if we're already beyond screen width
break
end
end
Expand All @@ -1009,6 +1027,13 @@ function alignment(
return a
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
Expand All @@ -1023,13 +1048,18 @@ function print_matrix_row(io::IO,
a = undef_ref_alignment
sx = undef_ref_str
end
l = repeat(" ", A[k][1]-a[1])
l = repeat(" ", A[k][1]-a[1]) # pad on left and right as needed
r = repeat(" ", A[k][2]-a[2])
print(io, l, sx, 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
)
Expand All @@ -1046,6 +1076,16 @@ function print_matrix_vdots(io::IO,
end
end

"""
`print_matrix(io, X)` composes an entire matrix X, taking into account the screen size
to determine when vertical, horizontal, or diagonal ellipsis are desired.
`print_matrix(io, X, sz, pre, sep, post, vdots, ddots, hmod)` has optional
parameters: screen size tuple `sz` such as (24,80),
string `pre` prior to the matrix, with same-size indent on following rows,
and string `post` on the end of the last row of the matrix.
Also options to use different ellipsis characters `hdots`,
`vdots`, `ddots`. The ellipsis are separated every `hmod` or `vmod` apart.
"""
function print_matrix(io::IO, X::AbstractVecOrMat,
sz::Tuple{Integer, Integer} = (s = tty_size(); (s[1]-4, s[2])),
pre::AbstractString = " ",
Expand All @@ -1062,20 +1102,23 @@ function print_matrix(io::IO, X::AbstractVecOrMat,
@assert strwidth(hdots) == strwidth(ddots)
ss = length(sep)
m, n = size(X,1), size(X,2)
if m <= rows # rows fit
if m <= rows # rows fit vertically on screen
# is there a reasonable chance of fitting all columns across screen?
# evaluate by assuming minimum width of 1 char and separator per element

A = alignment(X,1:m,1:n,cols,cols,ss)
if n <= length(A) # rows and cols fit
if n <= length(A) # rows and cols fit so just print whole matrix in one piece
for i = 1:m
print(io, i == 1 ? pre : presp)
print_matrix_row(io, X,A,i,1:n,sep)
print(io, i == m ? post : postsp)
if i != m; println(io, ); end
end
else # rows fit, cols don't
c = div(cols-length(hdots)+1,2)+1
R = reverse(alignment(X,1:m,n:-1:1,c,c,ss))
else # rows fit on screen but cols don't, so need horizontal ellipsis
c = div(cols-length(hdots)+1,2)+1 # what goes to right of ellipsis
R = reverse(alignment(X,1:m,n:-1:1,c,c,ss)) # alignments for right
c = cols - sum(map(sum,R)) - (length(R)-1)*ss - length(hdots)
L = alignment(X,1:m,1:n,c,c,ss)
L = alignment(X,1:m,1:n,c,c,ss) # alignments for left of ellipsis
for i = 1:m
print(io, i == 1 ? pre : presp)
print_matrix_row(io, X,L,i,1:length(L),sep)
Expand All @@ -1085,11 +1128,11 @@ function print_matrix(io::IO, X::AbstractVecOrMat,
if i != m; println(io, ); end
end
end
else # rows don't fit
else # rows don't fit so will need vertical ellipsis
t = div(rows,2)
I = [1:t; m-div(rows-1,2)+1:m]
A = alignment(X,I,1:n,cols,cols,ss)
if n <= length(A) # rows don't fit, cols do
if n <= length(A) # rows don't fit, cols do, so only vertical ellipsis
for i in I
print(io, i == 1 ? pre : presp)
print_matrix_row(io, X,A,i,1:n,sep)
Expand All @@ -1101,7 +1144,7 @@ function print_matrix(io::IO, X::AbstractVecOrMat,
println(io, i == m ? post : postsp)
end
end
else # neither rows nor cols fit
else # neither rows nor cols fit, so use all 3 kinds of ellipsis
c = div(cols-length(hdots)+1,2)+1
R = reverse(alignment(X,I,n:-1:1,c,c,ss))
c = cols - sum(map(sum,R)) - (length(R)-1)*ss - length(hdots)
Expand All @@ -1126,15 +1169,20 @@ function print_matrix(io::IO, X::AbstractVecOrMat,
end
end

summary(x) = string(typeof(x))
"`summary(x)` a string of type information, e.g. `Int64`"
summary(x) = string(typeof(x)) # e.g. Int64

# sizes such as 0-dimensional, 4-dimensional, 2x3
dims2string(d) = length(d) == 0 ? "0-dimensional" :
length(d) == 1 ? "$(d[1])-element" :
join(map(string,d), 'x')

# anything array-like gets summarized e.g. 10-element Array{Int64,1}
"`summary(A)` for array is a string of size and type info, e.g. `10-element Array{Int64,1}`"
summary(a::AbstractArray) =
string(dims2string(size(a)), " ", typeof(a))

# n-dimensional arrays
function show_nd(io::IO, a::AbstractArray, limit, print_matrix, label_slices)
if isempty(a)
return
Expand Down Expand Up @@ -1181,6 +1229,7 @@ end
# for internal use in showing arrays.
_limit_output = false

# print matrix with opening and closing square brackets
function print_matrix_repr(io, X::AbstractArray)
compact, prefix = array_eltype_show_how(X)
prefix *= "["
Expand Down Expand Up @@ -1247,7 +1296,7 @@ end

show(io::IO, X::AbstractArray) = showarray(io, X, header=_limit_output, repr=!_limit_output)

function with_output_limit(thk, lim=true)
function with_output_limit(thk, lim=true) # thk is usually show()
global _limit_output
last = _limit_output
_limit_output = lim
Expand Down
13 changes: 13 additions & 0 deletions test/ranges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,19 @@ for x in r
end
@test i == 7

# stringmime/writemime should display the range or linspace nicely
# to test print_range in range.jl
replstr(x) = stringmime("text/plain", x)
@test replstr(1:4) == "4-element UnitRange{Int64}:\n 1,2,3,4" ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably you want:

@test replstr(1:4) == "4-element UnitRange{$Int}:\n 1,2,3,4"

Similarly below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes thanks, I've committed the change.

replstr(1:4) == "4-element UnitRange{Int32}:\n 1,2,3,4"
@test replstr(linspace(1,5,7)) == "7-element LinSpace{Float64}:\n 1.0,1.66667,2.33333,3.0,3.66667,4.33333,5.0"
@test replstr(0:100.) == "101-element FloatRange{Float64}:\n 0.0,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,…,94.0,95.0,96.0,97.0,98.0,99.0,100.0"
# next is to test a very large range, which should be fast because print_range
# only examines spacing of the left and right edges of the range, sufficient
# to cover the designated screen size.
@test replstr(0:10^9) == "1000000001-element UnitRange{Int64}:\n 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,…,999999998,999999999,1000000000" ||
replstr(0:10^9) == "1000000001-element UnitRange{Int32}:\n 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,…,999999998,999999999,1000000000"

# Issue 11049 and related
@test promote(linspace(0f0, 1f0, 3), linspace(0., 5., 2)) ===
(linspace(0., 1., 3), linspace(0., 5., 2))
Expand Down