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

add :color attribute to IOContext, prefer get(io,:color,false) to Base.have_color #25067

Merged
merged 15 commits into from
Dec 18, 2017
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,10 @@ Library improvements
* The function `chop` now accepts two arguments `head` and `tail` allowing to specify
number of characters to remove from the head and tail of the string ([#24126]).

* `get(io, :color, false)` can now be used to query whether a stream `io` supports
[ANSI color codes](https://en.wikipedia.org/wiki/ANSI_escape_code) ([#25067]),
rather than using the undocumented `Base.have_color` global flag.

* Functions `first` and `last` now accept `nchar` argument for `AbstractString`.
If this argument is used they return a string consisting of first/last `nchar`
characters from the original string ([#23960]).
Expand Down
13 changes: 7 additions & 6 deletions base/logging.jl
Original file line number Diff line number Diff line change
Expand Up @@ -485,16 +485,17 @@ function handle_message(logger::SimpleLogger, level, message, _module, group, id
level < Warn ? :cyan :
level < Error ? :yellow : :red
buf = IOBuffer()
print_with_color(color, buf, first(levelstr), "- ", bold=true)
iob = IOContext(buf, logger.stream)
print_with_color(color, iob, first(levelstr), "- ", bold=true)
msglines = split(string(message), '\n')
for i in 1:length(msglines)-1
println(buf, msglines[i])
print_with_color(color, buf, "| ", bold=true)
println(iob, msglines[i])
print_with_color(color, iob, "| ", bold=true)
end
println(buf, msglines[end], " -", levelstr, ":", _module, ":", basename(filepath), ":", line)
println(iob, msglines[end], " -", levelstr, ":", _module, ":", basename(filepath), ":", line)
for (key,val) in pairs(kwargs)
print_with_color(color, buf, "| ", bold=true)
println(buf, key, " = ", val)
print_with_color(color, iob, "| ", bold=true)
println(iob, key, " = ", val)
end
write(logger.stream, take!(buf))
nothing
Expand Down
6 changes: 0 additions & 6 deletions base/markdown/Markdown.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,4 @@ macro doc_str(s::AbstractString, t...)
docexpr(__source__, __module__, s, t...)
end

function Base.display(d::Base.REPL.REPLDisplay, mds::Vector{MD})
for md in mds
display(d, md)
end
end

end
16 changes: 8 additions & 8 deletions base/markdown/render/terminal/formatting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ const text_formats = Dict(
:negative => ("\e[7m", "\e[27m"))

function with_output_format(f::Function, formats::Vector{Symbol}, io::IO, args...)
Base.have_color && for format in formats
get(io, :color, false) && for format in formats
haskey(text_formats, format) &&
print(io, text_formats[format][1])
end
try f(io, args...)
finally
Base.have_color && for format in formats
get(io, :color, false) && for format in formats
haskey(text_formats, format) &&
print(io, text_formats[format][2])
end
Expand Down Expand Up @@ -58,9 +58,9 @@ words(s) = split(s, " ")
lines(s) = split(s, "\n")

# This could really be more efficient
function wrapped_lines(s::AbstractString; width = 80, i = 0)
function wrapped_lines(io::IO, s::AbstractString; width = 80, i = 0)
if ismatch(r"\n", s)
return vcat(map(s->wrapped_lines(s, width = width, i = i), split(s, "\n"))...)
return vcat(map(s->wrapped_lines(io, s, width = width, i = i), split(s, "\n"))...)
end
ws = words(s)
lines = AbstractString[ws[1]]
Expand All @@ -78,11 +78,11 @@ function wrapped_lines(s::AbstractString; width = 80, i = 0)
return lines
end

wrapped_lines(f::Function, args...; width = 80, i = 0) =
wrapped_lines(sprint(f, args...), width = width, i = 0)
wrapped_lines(io::IO, f::Function, args...; width = 80, i = 0) =
wrapped_lines(io, sprint(f, args...; context=io), width = width, i = 0)

function print_wrapped(io::IO, s...; width = 80, pre = "", i = 0)
lines = wrapped_lines(s..., width = width, i = i)
lines = wrapped_lines(io, s..., width = width, i = i)
println(io, lines[1])
for line in lines[2:end]
println(io, pre, line)
Expand All @@ -93,7 +93,7 @@ end
print_wrapped(f::Function, io::IO, args...; kws...) = print_wrapped(io, f, args...; kws...)

function print_centred(io::IO, s...; columns = 80, width = columns)
lines = wrapped_lines(s..., width = width)
lines = wrapped_lines(io, s..., width = width)
for line in lines
print(io, " "^(div(columns-ansi_length(line), 2)))
println(io, line)
Expand Down
15 changes: 7 additions & 8 deletions base/markdown/render/terminal/render.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function term(io::IO, md::Paragraph, columns)
end

function term(io::IO, md::BlockQuote, columns)
s = sprint(term, md.content, columns - 10)
s = sprint(term, md.content, columns - 10; context=io)
for line in split(rstrip(s), "\n")
println(io, " "^margin, "|", line)
end
Expand All @@ -34,7 +34,7 @@ function term(io::IO, md::Admonition, columns)
print(io, " "^margin, "| ")
with_output_format(:bold, print, io, isempty(md.title) ? md.category : md.title)
println(io, "\n", " "^margin, "|")
s = sprint(term, md.content, columns - 10)
s = sprint(term, md.content, columns - 10; context=io)
for line in split(rstrip(s), "\n")
println(io, " "^margin, "|", line)
end
Expand All @@ -44,7 +44,7 @@ function term(io::IO, f::Footnote, columns)
print(io, " "^margin, "| ")
with_output_format(:bold, print, io, "[^$(f.id)]")
println(io, "\n", " "^margin, "|")
s = sprint(term, f.text, columns - 10)
s = sprint(term, f.text, columns - 10; context=io)
for line in split(rstrip(s), "\n")
println(io, " "^margin, "|", line)
end
Expand All @@ -61,7 +61,7 @@ function term(io::IO, md::List, columns)
end

function _term_header(io::IO, md, char, columns)
text = terminline(md.text)
text = terminline_string(io, md.text)
with_output_format(:bold, io) do io
print(io, " "^(margin))
line_no, lastline_width = print_wrapped(io, text,
Expand Down Expand Up @@ -103,7 +103,7 @@ term(io::IO, x, _) = show(io, MIME"text/plain"(), x)

# Inline Content

terminline(md) = sprint(terminline, md)
terminline_string(io::IO, md) = sprint(terminline, md; context=io)

terminline(io::IO, content...) = terminline(io, collect(content))

Expand Down Expand Up @@ -137,7 +137,7 @@ terminline(io::IO, f::Footnote) = with_output_format(:bold, terminline, io, "[^$

function terminline(io::IO, md::Link)
url = !Base.startswith(md.url, "@ref") ? " ($(md.url))" : ""
text = terminline(md.text)
text = terminline_string(io, md.text)
terminline(io, text, url)
end

Expand All @@ -148,5 +148,4 @@ end
terminline(io::IO, x) = show(io, MIME"text/plain"(), x)

# Show in terminal

Base.display(d::Base.REPL.REPLDisplay, md::MD) = term(Base.REPL.outstream(d.repl), md)
Base.show(io::IO, ::MIME"text/plain", md::MD) = (term(io, md); nothing)
2 changes: 1 addition & 1 deletion base/repl/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ end

function display(d::REPLDisplay, mime::MIME"text/plain", x)
io = outstream(d.repl)
Base.have_color && write(io, answer_color(d.repl))
get(io, :color, false) && write(io, answer_color(d.repl))
show(IOContext(io, :limit => true), mime, x)
println(io)
end
Expand Down
3 changes: 3 additions & 0 deletions base/repl/Terminals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,7 @@ else
end
end

# use cached value of have_color
Base.get(::TTYTerminal, k::Symbol, default) = k === :color ? Base.have_color : default

end # module
45 changes: 23 additions & 22 deletions base/replutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ end

function showerror(io::IO, ex, bt; backtrace=true)
try
with_output_color(have_color ? error_color() : :nothing, io) do io
with_output_color(get(io, :color, false) ? error_color() : :nothing, io) do io
showerror(io, ex)
end
finally
Expand Down Expand Up @@ -488,6 +488,7 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs::NamedTuple = Na
for (func,arg_types_param) in funcs
for method in methods(func)
buf = IOBuffer()
iob = IOContext(buf, io)
tv = Any[]
sig0 = method.sig
if Base.is_default_method(method)
Expand All @@ -499,20 +500,20 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs::NamedTuple = Na
end
s1 = sig0.parameters[1]
sig = sig0.parameters[2:end]
print(buf, " ")
print(iob, " ")
if !isa(func, s1)
# function itself doesn't match
return
else
# TODO: use the methodshow logic here
use_constructor_syntax = isa(func, Type)
print(buf, use_constructor_syntax ? func : typeof(func).name.mt.name)
print(iob, use_constructor_syntax ? func : typeof(func).name.mt.name)
end
print(buf, "(")
print(iob, "(")
t_i = copy(arg_types_param)
right_matches = 0
for i = 1 : min(length(t_i), length(sig))
i > 1 && print(buf, ", ")
i > 1 && print(iob, ", ")
# If isvarargtype then it checks whether the rest of the input arguments matches
# the varargtype
if Base.isvarargtype(sig[i])
Expand All @@ -529,20 +530,20 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs::NamedTuple = Na
# the type of the first argument is not matched.
t_in === Union{} && special && i == 1 && break
if t_in === Union{}
if Base.have_color
Base.with_output_color(Base.error_color(), buf) do buf
print(buf, "::$sigstr")
if get(io, :color, false)
Base.with_output_color(Base.error_color(), iob) do iob
print(iob, "::$sigstr")
end
else
print(buf, "!Matched::$sigstr")
print(iob, "!Matched::$sigstr")
end
# If there is no typeintersect then the type signature from the method is
# inserted in t_i this ensures if the type at the next i matches the type
# signature then there will be a type intersect
t_i[i] = sig[i]
else
right_matches += j==i ? 1 : 0
print(buf, "::$sigstr")
print(iob, "::$sigstr")
end
end
special && right_matches==0 && return # continue the do-block
Expand All @@ -569,26 +570,26 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs::NamedTuple = Na
sigstr = string(sigtype)
end
if !((min(length(t_i), length(sig)) == 0) && k==1)
print(buf, ", ")
print(iob, ", ")
end
if Base.have_color
Base.with_output_color(Base.error_color(), buf) do buf
print(buf, "::$sigstr")
if get(io, :color, false)
Base.with_output_color(Base.error_color(), iob) do iob
print(iob, "::$sigstr")
end
else
print(buf, "!Matched::$sigstr")
print(iob, "!Matched::$sigstr")
end
end
end
kwords = Symbol[]
if isdefined(ft.name.mt, :kwsorter)
kwsorter_t = typeof(ft.name.mt.kwsorter)
kwords = kwarg_decl(method, kwsorter_t)
length(kwords) > 0 && print(buf, "; ", join(kwords, ", "))
length(kwords) > 0 && print(iob, "; ", join(kwords, ", "))
end
print(buf, ")")
show_method_params(buf, tv)
print(buf, " at ", method.file, ":", method.line)
print(iob, ")")
show_method_params(iob, tv)
print(iob, " at ", method.file, ":", method.line)
if !isempty(kwargs)
unexpected = Symbol[]
if isempty(kwords) || !(any(endswith(string(kword), "...") for kword in kwords))
Expand All @@ -599,14 +600,14 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs::NamedTuple = Na
end
end
if !isempty(unexpected)
Base.with_output_color(Base.error_color(), buf) do buf
Base.with_output_color(Base.error_color(), iob) do iob
plur = length(unexpected) > 1 ? "s" : ""
print(buf, " got unsupported keyword argument$plur \"", join(unexpected, "\", \""), "\"")
print(iob, " got unsupported keyword argument$plur \"", join(unexpected, "\", \""), "\"")
end
end
end
if ex.world < min_world(method)
print(buf, " (method too new to be called from this world context.)")
print(iob, " (method too new to be called from this world context.)")
end
# TODO: indicate if it's in the wrong world
push!(lines, (buf, right_matches))
Expand Down
12 changes: 8 additions & 4 deletions base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ struct IOContext{IO_t <: IO} <: AbstractPipe
end
end

unwrapcontext(io::IO) = io, ImmutableDict{Symbol,Any}()
# (Note that TTY and TTYTerminal io types have a :color property.)
unwrapcontext(io::IO) = io, get(io,:color,false) ? ImmutableDict{Symbol,Any}(:color, true) : ImmutableDict{Symbol,Any}()
unwrapcontext(io::IOContext) = io.io, io.dict

function IOContext(io::IO, dict::ImmutableDict)
Expand Down Expand Up @@ -67,6 +68,9 @@ The following properties are in common use:
can be avoided (e.g. `[Float16(0)]` can be shown as "Float16[0.0]" instead
of "Float16[Float16(0.0)]" : while displaying the elements of the array, the `:typeinfo`
property will be set to `Float16`).
- `:color`: Boolean specifying whether ANSI color/escape codes are supported/expected.
Copy link
Member

Choose a reason for hiding this comment

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

Longer term, I think we may want to try to distinguish between policy ("wants color") and mechanism ("ansi", "xterm256", "html"). But this PR is already strictly better than what we have now, so I think we should merge this and then consider iterating (as needed).

Copy link
Member

Choose a reason for hiding this comment

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

We can add new IO context attributes that Base understand in the future as long as the behavior of the existing ones remains the same.

By default, this is determined by whether `io` is a compatible terminal and by any
`--color` command-line flag when `julia` was launched.

# Examples
```jldoctest
Expand Down Expand Up @@ -715,7 +719,7 @@ function show_expr_type(io::IO, @nospecialize(ty), emph::Bool)
end
end

emphasize(io, str::AbstractString) = have_color ?
emphasize(io, str::AbstractString) = get(io, :color, false) ?
print_with_color(Base.error_color(), io, str; bold = true) :
print(io, Unicode.uppercase(str))

Expand Down Expand Up @@ -1253,7 +1257,7 @@ end

function show_tuple_as_call(io::IO, name::Symbol, sig::Type)
# print a method signature tuple for a lambda definition
color = have_color && get(io, :backtrace, false) ? stackframe_function_color() : :nothing
color = get(io, :color, false) && get(io, :backtrace, false) ? stackframe_function_color() : :nothing
if sig === Tuple
Base.print_with_color(color, io, name, "(...)")
return
Expand All @@ -1274,7 +1278,7 @@ function show_tuple_as_call(io::IO, name::Symbol, sig::Type)
end
end
first = true
print_style = have_color && get(io, :backtrace, false) ? :bold : :nothing
print_style = get(io, :color, false) && get(io, :backtrace, false) ? :bold : :nothing
print_with_color(print_style, io, "(")
for i = 2:length(sig) # fixme (iter): `eachindex` with offset?
first || print(io, ", ")
Expand Down
4 changes: 2 additions & 2 deletions base/stacktraces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ function show_spec_linfo(io::IO, frame::StackFrame)
elseif frame.func === top_level_scope_sym
print(io, "top-level scope")
else
print_with_color(Base.have_color && get(io, :backtrace, false) ? Base.stackframe_function_color() : :nothing, io, string(frame.func))
print_with_color(get(io, :color, false) && get(io, :backtrace, false) ? Base.stackframe_function_color() : :nothing, io, string(frame.func))
end
elseif frame.linfo isa Core.MethodInstance
if isa(frame.linfo.def, Method)
Expand All @@ -323,7 +323,7 @@ function show(io::IO, frame::StackFrame; full_path::Bool=false)
if frame.file !== empty_sym
file_info = full_path ? string(frame.file) : basename(string(frame.file))
print(io, " at ")
Base.with_output_color(Base.have_color && get(io, :backtrace, false) ? Base.stackframe_lineinfo_color() : :nothing, io) do io
Base.with_output_color(get(io, :color, false) && get(io, :backtrace, false) ? Base.stackframe_lineinfo_color() : :nothing, io) do io
print(io, file_info, ":")
if frame.line >= 0
print(io, frame.line)
Expand Down
1 change: 1 addition & 0 deletions base/stream.jl
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ function displaysize(io::TTY)
return h, w
end

get(::TTY, k::Symbol, default) = k === :color ? have_color : default

### Libuv callbacks ###

Expand Down
9 changes: 5 additions & 4 deletions base/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,13 @@ end

function with_output_color(f::Function, color::Union{Int, Symbol}, io::IO, args...; bold::Bool = false)
buf = IOBuffer()
have_color && bold && print(buf, text_colors[:bold])
have_color && print(buf, get(text_colors, color, color_normal))
iscolor = get(io, :color, false)
iscolor && bold && print(buf, text_colors[:bold])
iscolor && print(buf, get(text_colors, color, color_normal))
try f(IOContext(buf, io), args...)
finally
have_color && color != :nothing && print(buf, get(disable_text_style, color, text_colors[:default]))
have_color && (bold || color == :bold) && print(buf, disable_text_style[:bold])
iscolor && color != :nothing && print(buf, get(disable_text_style, color, text_colors[:default]))
iscolor && (bold || color == :bold) && print(buf, disable_text_style[:bold])
print(io, String(take!(buf)))
end
end
Expand Down
2 changes: 1 addition & 1 deletion base/version.jl
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ function banner(io::IO = STDOUT)
end
commit_date = !isempty(GIT_VERSION_INFO.date_string) ? " ($(GIT_VERSION_INFO.date_string))" : ""

if have_color
if get(io, :color, false)
c = text_colors
tx = c[:normal] # text
jl = c[:normal] # julia
Expand Down
Loading