Skip to content

Commit

Permalink
reimplement color as a property of printing to a particular stream, i…
Browse files Browse the repository at this point in the history
…nstead of a global variable
  • Loading branch information
vtjnash committed Nov 17, 2015
1 parent 03bd951 commit 96b83ad
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 55 deletions.
53 changes: 22 additions & 31 deletions base/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import ..LineEdit:

abstract AbstractREPL

answer_color(::AbstractREPL) = ""
answer_color_symbol(::AbstractREPL) = :plain

type REPLBackend
repl_channel::Channel
Expand Down Expand Up @@ -109,18 +109,18 @@ end
==(a::REPLDisplay, b::REPLDisplay) = a.repl === b.repl

function display(d::REPLDisplay, ::MIME"text/plain", x)
io = outstream(d.repl)
Base.have_color && write(io, answer_color(d.repl))
writemime(io, MIME("text/plain"), x)
println(io)
Base.with_output_color(answer_color_symbol(d.repl), outstream(d.repl)) do io
writemime(io, MIME("text/plain"), x)
println(io)
end
end
display(d::REPLDisplay, x) = display(d, MIME("text/plain"), x)

function print_response(repl::AbstractREPL, val::ANY, bt, show_value::Bool, have_color::Bool)
function print_response(repl::AbstractREPL, val::ANY, bt, show_value::Bool)
repl.waserror = bt !== nothing
print_response(outstream(repl), val, bt, show_value, have_color, specialdisplay(repl))
print_response(outstream(repl), val, bt, show_value, specialdisplay(repl))
end
function print_response(errio::IO, val::ANY, bt, show_value::Bool, have_color::Bool, specialdisplay=nothing)
function print_response(errio::IO, val::ANY, bt, show_value::Bool, specialdisplay=nothing)
while true
try
if bt !== nothing
Expand Down Expand Up @@ -215,7 +215,7 @@ function run_frontend(repl::BasicREPL, backend::REPLBackendRef)
put!(repl_channel, (ast, 1))
val, bt = take!(response_channel)
if !ends_with_semicolon(line)
print_response(repl, val, bt, true, false)
print_response(repl, val, bt, true)
end
end
write(repl.terminal, '\n')
Expand All @@ -233,7 +233,7 @@ type LineEditREPL <: AbstractREPL
hascolor::Bool
prompt_color::AbstractString
input_color::AbstractString
answer_color::AbstractString
answer_color_symbol::Symbol
shell_color::AbstractString
help_color::AbstractString
history_file::Bool
Expand All @@ -248,16 +248,16 @@ type LineEditREPL <: AbstractREPL
new(t,true,prompt_color,input_color,answer_color,shell_color,help_color,history_file,in_shell,
in_help,envcolors,false,nothing)
end
outstream(r::LineEditREPL) = r.t
outstream(r::LineEditREPL) = r.hascolor ? IOContext(r.t, :ansi => true) : r.t
specialdisplay(r::LineEditREPL) = r.specialdisplay
specialdisplay(r::AbstractREPL) = nothing
terminal(r::LineEditREPL) = r.t

LineEditREPL(t::TextTerminal, envcolors = false) = LineEditREPL(t,
true,
julia_green,
Base.text_colors[:green],
Base.input_color(),
Base.answer_color(),
Base.answer_color_symbol(),
Base.text_colors[:red],
Base.text_colors[:yellow],
false, false, false, envcolors)
Expand Down Expand Up @@ -579,8 +579,6 @@ function history_reset_state(hist::REPLHistoryProvider)
end
LineEdit.reset_state(hist::REPLHistoryProvider) = history_reset_state(hist)

const julia_green = "\033[1m\033[32m"

function return_callback(s)
ast = Base.syntax_deprecation_warnings(false) do
Base.parse_input_line(bytestring(LineEdit.buffer(s)))
Expand Down Expand Up @@ -621,7 +619,7 @@ function respond(f, repl, main; pass_empty = false)
reset(repl)
val, bt = send_to_backend(f(line), backend(repl))
if !ends_with_semicolon(line) || bt !== nothing
print_response(repl, val, bt, true, Base.have_color)
print_response(repl, val, bt, true)
end
end
prepare_next(repl)
Expand Down Expand Up @@ -748,7 +746,7 @@ function setup_interface(repl::LineEditREPL; hascolor = repl.hascolor, extra_rep
finalizer(replc, replc->close(f))
hist_from_file(hp, f)
catch e
print_response(repl, e, catch_backtrace(), true, Base.have_color)
print_response(repl, e, catch_backtrace(), true)
println(outstream(repl))
info("Disabling history file for this session.")
repl.history_file = false
Expand Down Expand Up @@ -865,30 +863,23 @@ function run_frontend(repl::LineEditREPL, backend)
dopushdisplay && popdisplay(d)
end

if isdefined(Base, :banner_color)
banner(io, t) = banner(io, hascolor(t))
banner(io, x::Bool) = print(io, x ? Base.banner_color : Base.banner_plain)
else
banner(io,t) = Base.banner(io)
end

## StreamREPL ##

type StreamREPL <: AbstractREPL
stream::IO
prompt_color::AbstractString
input_color::AbstractString
answer_color::AbstractString
answer_color_symbol::Symbol
waserror::Bool
StreamREPL(stream,pc,ic,ac) = new(stream,pc,ic,ac,false)
end

outstream(s::StreamREPL) = s.stream

StreamREPL(stream::IO) = StreamREPL(stream, julia_green, Base.text_colors[:white], Base.answer_color())
StreamREPL(stream::IO) = StreamREPL(stream, Base.text_colors[:green], Base.text_colors[:white], Base.answer_color_symbol())

answer_color(r::LineEditREPL) = r.envcolors ? Base.answer_color() : r.answer_color
answer_color(r::StreamREPL) = r.answer_color
answer_color_symbol(r::LineEditREPL) = r.envcolors ? Base.answer_color_symbol() : r.answer_color_symbol
answer_color_symbol(r::StreamREPL) = r.answer_color_symbol
input_color(r::LineEditREPL) = r.envcolors ? Base.input_color() : r.input_color
input_color(r::StreamREPL) = r.input_color

Expand Down Expand Up @@ -917,14 +908,14 @@ end

function run_frontend(repl::StreamREPL, backend::REPLBackendRef)
have_color = Base.have_color
banner(repl.stream, have_color)
Base.banner(repl.stream)
d = REPLDisplay(repl)
dopushdisplay = !in(d,Base.Multimedia.displays)
dopushdisplay && pushdisplay(d)
repl_channel, response_channel = backend.repl_channel, backend.response_channel
while repl.stream.open
if have_color
print(repl.stream,repl.prompt_color)
print(repl.stream, repl.prompt_color)
end
print(repl.stream, "julia> ")
if have_color
Expand All @@ -939,7 +930,7 @@ function run_frontend(repl::StreamREPL, backend::REPLBackendRef)
put!(repl_channel, (ast, 1))
val, bt = take!(response_channel)
if !ends_with_semicolon(line)
print_response(repl, val, bt, true, have_color)
print_response(repl, val, bt, true)
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions base/Terminals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -181,5 +181,9 @@ eof(t::UnixTerminal) = eof(t.in_stream)

@unix_only hascolor(t::TTYTerminal) = (startswith(t.term_type, "xterm") || success(`tput setaf 0`))
@windows_only hascolor(t::TTYTerminal) = true
""" hascolor(t::TTYTerminal)
Returns whether terminal `t` supports ANSI formatting codes
(however, this does not indicate whether they should be used during printing)"""
hascolor

end # module
8 changes: 5 additions & 3 deletions base/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ end

warn_color() = repl_color("JULIA_WARN_COLOR", default_color_warn)
info_color() = repl_color("JULIA_INFO_COLOR", default_color_info)
input_color() = text_colors[repl_color("JULIA_INPUT_COLOR", default_color_input)]
answer_color() = text_colors[repl_color("JULIA_ANSWER_COLOR", default_color_answer)]
input_color_symbol() = repl_color("JULIA_INPUT_COLOR", default_color_input)
input_color() = text_colors[input_color_symbol()]
answer_color_symbol() = repl_color("JULIA_ANSWER_COLOR", default_color_answer)
answer_color() = text_colors[answer_color_symbol()]

exit(n) = ccall(:jl_exit, Void, (Int32,), n)
exit() = exit(0)
Expand Down Expand Up @@ -387,7 +389,7 @@ function _start()
term = Terminals.TTYTerminal(get(ENV,"TERM",@windows? "" : "dumb"),STDIN,STDOUT,STDERR)
global is_interactive = true
color_set || (global have_color = Terminals.hascolor(term))
quiet || REPL.banner(term,term)
quiet || banner(term)
if term.term_type == "dumb"
active_repl = REPL.BasicREPL(term)
quiet || warn("Terminal not fully functional")
Expand Down
20 changes: 6 additions & 14 deletions base/replutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ function show_method_candidates(io::IO, ex::MethodError)
name = isgeneric(func) ? func.env.name : :anonymous
for method in methods(func)
haskey(UNSHOWN_METHODS, method) && continue
buf = IOBuffer()
buf = with_formatter(io, :normal)[1]
sig = method.sig.parameters
use_constructor_syntax = func == call && !isempty(sig) && isa(sig[1], DataType) &&
!isempty(sig[1].parameters) && isa(sig[1].parameters[1], DataType)
Expand Down Expand Up @@ -283,12 +283,8 @@ function show_method_candidates(io::IO, ex::MethodError)
if use_constructor_syntax && i == 1
right_matches += i
elseif t_in === Union{}
if Base.have_color
Base.with_output_color(:red, buf) do buf
print(buf, "::$sigstr")
end
else
print(buf, "!Matched::$sigstr")
Base.print_with_color(:red, buf, "::$sigstr") do buf, sig
print(buf, "!Matched$sig")
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
Expand Down Expand Up @@ -322,12 +318,8 @@ function show_method_candidates(io::IO, ex::MethodError)
sigstr = string(sigtype)
end
print(buf, ", ")
if Base.have_color
Base.with_output_color(:red, buf) do buf
print(buf, "::$sigstr")
end
else
print(buf, "!Matched::$sigstr")
Base.print_with_color(:red, buf, "::$sigstr") do buf, sig
print(buf, "!Matched$sig")
end
end
end
Expand All @@ -350,7 +342,7 @@ function show_method_candidates(io::IO, ex::MethodError)
break
end
i += 1
print(io, takebuf_string(line[1]))
finalize_formatter(io, line[1])
end
end
end
Expand Down
4 changes: 3 additions & 1 deletion base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ pipe_reader(io::IOContext) = io.io
pipe_writer(io::IOContext) = io.io
lock(io::IOContext) = lock(io.io)
unlock(io::IOContext) = unlock(io.io)
takebuf_string(io::IOContext) = takebuf_string(io.io)
takebuf_array(io::IOContext) = takebuf_array(io.io)

function in(key_value::Pair, io::IOContext)
key, value = key_value
Expand Down Expand Up @@ -450,7 +452,7 @@ function show_expr_type(io::IO, ty, emph)
end
end

emphasize(io, str::AbstractString) = have_color ? print_with_color(:red, io, str) : print(io, uppercase(str))
emphasize(io, str::AbstractString) = print_with_color(:red, io, str) do io, str; print(io, uppercase(str)); end

show_linenumber(io::IO, file, line) = print(io," # ", file,", line ",line,':')

Expand Down
112 changes: 106 additions & 6 deletions base/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -307,25 +307,125 @@ end

## printing with color ##

function with_output_color(f::Function, color::Symbol, io::IO, args...)
buf = IOBuffer()
have_color && print(buf, get(text_colors, color, color_normal))
try f(buf, args...)
with_output_color(f::Function, color::Symbol, io::IO, args...) = with_output_color(f, f, color, io, args...)
function with_output_color(f::Function, alt::Function, color::Symbol, io::IO, args...)
buf, known = with_formatter(io, color)
try
known ? f(buf, args...) : alt(buf, args...)
finally
have_color && print(buf, color_normal)
print(io, takebuf_string(buf))
finalize_formatter(io, buf)
end
end

print_with_color(color::Symbol, io::IO, msg::AbstractString...) =
with_output_color(print, color, io, msg...)
print_with_color(alt::Function, color::Symbol, io::IO, msg::AbstractString...) =
with_output_color(print, alt, color, io, msg...)
print_with_color(color::Symbol, msg::AbstractString...) =
print_with_color(color, STDOUT, msg...)

println_with_color(color::Symbol, io::IO, msg::AbstractString...) =
with_output_color(println, color, io, msg...)
println_with_color(alt::Function, color::Symbol, io::IO, msg::AbstractString...) =
with_output_color(println, alt, color, io, msg...)
println_with_color(color::Symbol, msg::AbstractString...) =
println_with_color(color, STDOUT, msg...)

# Basic text formatter (ignores most fmt tags)
function with_formatter(io::IO, fmt::Symbol)
buf = IOBuffer()
fmt === :para && print(buf, "\n")
return buf, fmt === :plain || fmt === :para
end
function finalize_formatter(io::IO, fmt_io::IO)
print(io, takebuf_string(fmt_io))
end

# ANSI text formatter (or pass-through)
ansi_formatted(io::IOContext) = get(io, :ansi, false) === true
function with_formatter(io::IOContext, fmt::Symbol)
# try the formatter for io.io
buf, known = with_formatter(io.io, fmt)
buf = IOContext(buf, io)
if ansi_formatted(io)
if known
# skip ansi formatting if it was handled by the inner formatter
fmt = ""
else
fmt = get(text_colors, fmt, "")
# non-existent format
isempty(fmt) && return buf, false
print(buf, fmt)
end
# record current format string
return IOContext(buf, :ansi_previous => fmt), true
end
return buf, known
end
function finalize_formatter(io::IOContext, fmt_io::IO)
if ansi_formatted(io)
# if ansi formatting was enabled, restore the previous ansi state
prev = get(io, :ansi_previous, color_normal)
isempty(prev) || print(fmt_io, prev)
end
# commit and finalize the result
finalize_formatter(io.io, fmt_io)
end

# HTML text streaming formatter
const html_tags = Dict{Symbol, Tuple{ASCIIString, ASCIIString}}([
:para => ("<div>", "</div>"),
:plain => ("<span>", "</span>"),
:bold => ("<b>", "</b>"),
:italic => ("<i>", "</i>"),
:blue => ("<span style='color:blue'>", "</span>"),
:red => ("<span style='color:red'>", "</span>"),
:green => ("<span style='color:green'>", "</span>"),
:white => ("<span style='color:white'>", "</span>"),
:black => ("<span style='color:black'>", "</span>"),
:yellow => ("<span style='color:yellow'>", "</span>"),
:magenta => ("<span style='color:magenta'>", "</span>"),
:cyan => ("<span style='color:cyan'>", "</span>"),
])
type HTMLBuilder <: AbstractPipe
tag::Symbol
children::Vector{Union{ByteString, HTMLBuilder}}
buf::IOBuffer
HTMLBuilder(tag::Symbol) = new(tag, fieldtype(HTMLBuilder, :children)(), IOBuffer())
end
pipe_writer(io::HTMLBuilder) = io.buf
pipe_reader(io::HTMLBuilder) = error("HTMLBuffer not byte-readable")
takebuf_string(io::HTMLBuilder) = io
function with_formatter(io::HTMLBuilder, fmt::Symbol)
return HTMLBuilder(fmt), fmt in keys(html_tags)
end
function finalize_formatter(io::HTMLBuilder, fmt_io::IO)
print(io, takebuf_string(fmt_io))
end
html_escape(str::ByteString) = replace(replace(replace(str, "<", "&lt;"), ">", "&gt;"), "\n", "<br>") # TODO
function flush!(io::HTMLBuilder)
nb_available(io.buf) > 0 && push!(io.children, html_escape(takebuf_string(io.buf)))
nothing
end
function print(io::HTMLBuilder, html::HTMLBuilder)
flush!(io)
push!(io.children, html)
end
function print(io::HTMLBuilder, html::ByteString)
flush!(io)
push!(io.children, html_escape(html))
end
function print(io::IO, html::HTMLBuilder)
tags = get(html_tags, html.tag, ("", ""))
print(io, tags[1])
for child in html.children
print(io, child)
end
print(io, html_escape(takebuf_string(html.buf)))
print(io, tags[2])
end


## warnings and messages ##

function info(io::IO, msg...; prefix="INFO: ")
Expand Down

0 comments on commit 96b83ad

Please sign in to comment.