Skip to content

Commit 4dc6f35

Browse files
committed
print error messages in REPL with colors and structure
1 parent aea6e89 commit 4dc6f35

8 files changed

+71
-46
lines changed

base/REPL.jl

+8-8
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,13 @@ function ip_matches_func(ip, func::Symbol)
111111
end
112112

113113
function display_error(io::IO, er, bt)
114-
Base.with_output_color(:red, io) do io
115-
print(io, "ERROR: ")
116-
# remove REPL-related frames from interactive printing
117-
eval_ind = findlast(addr->ip_matches_func(addr, :eval), bt)
118-
if eval_ind != 0
119-
bt = bt[1:eval_ind-1]
120-
end
121-
Base.showerror(IOContext(io, :limit => true), er, bt)
114+
print_with_color(:red, io, "ERROR: ")
115+
# remove REPL-related frames from interactive printing
116+
eval_ind = findlast(addr->Base.REPL.ip_matches_func(addr, :eval), bt)
117+
if eval_ind != 0
118+
bt = bt[1:eval_ind-1]
122119
end
120+
showerror(IOContext(IOContext(io, :hascolor => true), :limit => true), er, bt)
123121
end
124122

125123
immutable REPLDisplay{R<:AbstractREPL} <: Display
@@ -167,6 +165,8 @@ function print_response(errio::IO, val::ANY, bt, show_value::Bool, have_color::B
167165
catch err
168166
if bt !== nothing
169167
println(errio, "SYSTEM: show(lasterr) caused an error")
168+
println(errio, err)
169+
Base.show_backtrace(errio, bt)
170170
break
171171
end
172172
val = err

base/client.jl

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const text_colors = AnyDict(
1414
:white => "\033[1m\033[37m",
1515
:normal => "\033[0m",
1616
:bold => "\033[1m",
17+
:nothing => "",
1718
)
1819

1920
for i in 0:255
@@ -35,6 +36,7 @@ const available_text_colors_docstring =
3536
"""Dictionary of color codes for the terminal.
3637
3738
Available colors are: $available_text_colors_docstring as well as the integers 0 to 255 inclusive.
39+
Printing with the color `:nothing` will print the string without modifications.
3840
"""
3941
text_colors
4042

@@ -61,6 +63,8 @@ warn_color() = repl_color("JULIA_WARN_COLOR", default_color_warn)
6163
info_color() = repl_color("JULIA_INFO_COLOR", default_color_info)
6264
input_color() = text_colors[repl_color("JULIA_INPUT_COLOR", default_color_input)]
6365
answer_color() = text_colors[repl_color("JULIA_ANSWER_COLOR", default_color_answer)]
66+
stackframe_linfo_color() = repl_color("JULIA_STACKFRAME_LINFO_COLOR", :bold)
67+
stackframe_function_color() = repl_color("JULIA_STACKFRAME_FUNCTION_COLOR", :bold)
6468

6569
function repl_cmd(cmd, out)
6670
shell = shell_split(get(ENV,"JULIA_SHELL",get(ENV,"SHELL","/bin/sh")))

base/replutil.jl

+17-6
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,13 @@ end
194194

195195
function showerror(io::IO, ex, bt; backtrace=true)
196196
try
197-
showerror(io, ex)
197+
with_output_color(get(io, :hascolor, false) ? :red : :nothing, io) do io
198+
showerror(io, ex)
199+
end
198200
finally
199-
backtrace && show_backtrace(io, bt)
201+
if backtrace
202+
show_backtrace(io, bt)
203+
end
200204
end
201205
end
202206

@@ -559,15 +563,22 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs::Vector=Any[])
559563
end
560564
end
561565

562-
function show_trace_entry(io, frame, n)
566+
function show_trace_entry(io, frame, n; prefix = " in ")
563567
print(io, "\n")
564-
show(io, frame, full_path=true)
568+
show(io, frame, full_path=true; prefix = prefix)
565569
n > 1 && print(io, " (repeats ", n, " times)")
566570
end
567571

568572
function show_backtrace(io::IO, t::Vector)
569-
process_entry(last_frame, n) =
570-
show_trace_entry(io, last_frame, n)
573+
n_frames = 0
574+
frame_counter = 0
575+
process_backtrace((a,b) -> n_frames += 1, t)
576+
n_frames != 0 && print(io, "\n\nStacktrace:")
577+
process_entry = (last_frame, n) -> begin
578+
frame_counter += 1
579+
n_spaces_align = ndigits(n_frames) - ndigits(frame_counter) + 1
580+
show_trace_entry(io, last_frame, n, prefix = string(" "^n_spaces_align, "[", frame_counter, "] "))
581+
end
571582
process_backtrace(process_entry, t)
572583
end
573584

base/show.jl

+24-18
Original file line numberDiff line numberDiff line change
@@ -1030,31 +1030,37 @@ end
10301030

10311031
function show_lambda_types(io::IO, li::Core.MethodInstance)
10321032
# print a method signature tuple for a lambda definition
1033-
if li.specTypes === Tuple
1034-
print(io, li.def.name, "(...)")
1035-
return
1036-
end
1037-
1038-
sig = li.specTypes.parameters
1039-
ft = sig[1]
1040-
if ft <: Function && isempty(ft.parameters) &&
1041-
isdefined(ft.name.module, ft.name.mt.name) &&
1042-
ft == typeof(getfield(ft.name.module, ft.name.mt.name))
1043-
print(io, ft.name.mt.name)
1044-
elseif isa(ft, DataType) && ft.name === Type.name && isleaftype(ft)
1045-
f = ft.parameters[1]
1046-
print(io, f)
1047-
else
1048-
print(io, "(::", ft, ")")
1033+
local sig
1034+
returned_from_do = false
1035+
Base.with_output_color(get(io, :hascolor, false) ? stackframe_function_color() : :nothing, io) do io
1036+
if li.specTypes === Tuple
1037+
print(io, li.def.name, "(...)")
1038+
returned_from_do = true
1039+
return
1040+
end
1041+
sig = li.specTypes.parameters
1042+
ft = sig[1]
1043+
if ft <: Function && isempty(ft.parameters) &&
1044+
isdefined(ft.name.module, ft.name.mt.name) &&
1045+
ft == typeof(getfield(ft.name.module, ft.name.mt.name))
1046+
print(io, ft.name.mt.name)
1047+
elseif isa(ft, DataType) && ft.name === Type.name && isleaftype(ft)
1048+
f = ft.parameters[1]
1049+
print(io, f)
1050+
else
1051+
print(io, "(::", ft, ")")
1052+
end
10491053
end
1054+
returned_from_do && return
10501055
first = true
1051-
print(io, '(')
1056+
print_style = get(io, :hascolor, false) ? :bold : :nothing
1057+
print_with_color(print_style, io, "(")
10521058
for i = 2:length(sig) # fixme (iter): `eachindex` with offset?
10531059
first || print(io, ", ")
10541060
first = false
10551061
print(io, "::", sig[i])
10561062
end
1057-
print(io, ')')
1063+
print_with_color(print_style, io, ")")
10581064
nothing
10591065
end
10601066

base/stacktraces.jl

+12-8
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ function show_spec_linfo(io::IO, frame::StackFrame)
188188
if frame.func === empty_sym
189189
@printf(io, "ip:%#x", frame.pointer)
190190
else
191-
print(io, frame.func)
191+
print_with_color(get(io, :hascolor, false) ? Base.stackframe_function_color() : :nothing, io, string(frame.func))
192192
end
193193
else
194194
linfo = get(frame.linfo)
@@ -200,16 +200,20 @@ function show_spec_linfo(io::IO, frame::StackFrame)
200200
end
201201
end
202202

203-
function show(io::IO, frame::StackFrame; full_path::Bool=false)
204-
print(io, " in ")
203+
function show(io::IO, frame::StackFrame; full_path::Bool=false,
204+
prefix = " in ")
205+
print(io, prefix)
205206
show_spec_linfo(io, frame)
206207
if frame.file !== empty_sym
207208
file_info = full_path ? string(frame.file) : basename(string(frame.file))
208-
print(io, " at ", file_info, ":")
209-
if frame.line >= 0
210-
print(io, frame.line)
211-
else
212-
print(io, "?")
209+
print(io, " at ")
210+
Base.with_output_color(get(io, :hascolor, false) ? Base.stackframe_linfo_color() : :nothing, io) do io
211+
print(io, file_info, ":")
212+
if frame.line >= 0
213+
print(io, frame.line)
214+
else
215+
print(io, "?")
216+
end
213217
end
214218
end
215219
if frame.inlined

base/util.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ function with_output_color(f::Function, color::Union{Int, Symbol}, io::IO, args.
308308
have_color && print(buf, get(text_colors, color, color_normal))
309309
try f(IOContext(buf, io), args...)
310310
finally
311-
have_color && print(buf, color_normal)
311+
have_color && color != :nothing && print(buf, color_normal)
312312
print(io, String(take!(buf)))
313313
end
314314
end

test/cmdlineargs.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -328,12 +328,12 @@ end
328328
for precomp in ("yes", "no")
329329
bt = readstring(pipeline(ignorestatus(`$(Base.julia_cmd()) --startup-file=no --precompiled=$precomp
330330
-E 'include("____nonexistent_file")'`), stderr=catcmd))
331-
@test contains(bt, "in include_from_node1")
331+
@test contains(bt, "include_from_node1")
332332
if is_windows() && Sys.WORD_SIZE == 32 && precomp == "yes"
333333
# fixme, issue #17251
334-
@test_broken contains(bt, "in include_from_node1(::String) at $(joinpath(".","loading.jl"))")
334+
@test_broken contains(bt, "include_from_node1(::String) at $(joinpath(".","loading.jl"))")
335335
else
336-
@test contains(bt, "in include_from_node1(::String) at $(joinpath(".","loading.jl"))")
336+
@test contains(bt, "include_from_node1(::String) at $(joinpath(".","loading.jl"))")
337337
end
338338
lno = match(r"at \.[\/\\]loading\.jl:(\d+)", bt)
339339
@test length(lno.captures) == 1

test/compile.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ try
209209
end
210210
""")
211211

212-
t = redirected_stderr("ERROR: LoadError: Declaring __precompile__(false) is not allowed in files that are being precompiled.\n in __precompile__")
212+
t = redirected_stderr("ERROR: LoadError: Declaring __precompile__(false) is not allowed in files that are being precompiled.\n\nStacktrace:\n [1] __precompile__")
213213
try
214214
Base.compilecache("Baz") # from __precompile__(false)
215215
error("__precompile__ disabled test failed")
@@ -306,7 +306,7 @@ try
306306
error("break me")
307307
end
308308
""")
309-
t = redirected_stderr("ERROR: LoadError: break me\n in error")
309+
t = redirected_stderr("ERROR: LoadError: break me\n\nStacktrace:\n [1] error")
310310
try
311311
Base.require(:FooBar)
312312
error("\"LoadError: break me\" test failed")

0 commit comments

Comments
 (0)