On Thu, Jul 23, 2020 at 05:47:28PM -0400, David Malcolm wrote: > On Thu, 2020-07-23 at 12:28 -0400, Lewis Hyatt via Gcc-patches wrote: > > Hello- > > > > The attached patch is complete including docs, but I tagged as RFC > > because I am not sure if anyone will like it, or if the general > > reaction may > > be closer to recoiling in horror :). Would appreciate your thoughts, > > please... > > Thanks for working on this. I'm interested in other people's thoughts > on this. Various comments inline throughout below. > > Currently, if a UTF-8 locale is detected, GCC changes the quote > > characters > > it outputs in diagnostics to Unicode directional quotes. I feel like > > this is > > a nice touch, so I was wondering whether GCC shouldn't do more along > > these > > lines. This patch adds support for using Unicode line drawing > > characters and > > similar things when outputting diagnostics. There is a new option > > -fdiagnostics-unicode-drawing=[auto|never|always] to control it, > > which > > defaults to auto. "auto" will enable the feature under the same > > circumstances that Unicode quotes get output, namely when the locale > > is > > determined by gcc_init_libintl() to support UTF-8. (The new option > > does not > > affect Unicode quote characters, which currently are not configurable > > and > > are determined solely by the locale.) > > FWIW when I first started experimenting with location ranges back in > 2015 my first patches had box-drawing characters for underlines; you > can see this in some of the early examples here (and similar URLs from > around then): > > https://dmalcolm.fedorapeople.org/gcc/2015-08-18/plugin.html > (this also has a different approach for labeling ranges, which I > called "captions", putting them in a right margin) > > https://dmalcolm.fedorapeople.org/gcc/2015-08-19/diagnostic-test-string-literals-1.html > > https://dmalcolm.fedorapeople.org/gcc/2015-08-26/tree-expression-ranges.html > > etc; the patch kits were: > > https://gcc.gnu.org/legacy-ml/gcc-patches/2015-03/msg00837.html > https://gcc.gnu.org/pipermail/gcc-patches/2015-September/428036.html > https://gcc.gnu.org/legacy-ml/gcc-patches/2015-09/msg01696.html > > In: > https://gcc.gnu.org/legacy-ml/gcc-patches/2015-09/msg01700.html > I wrote: > > * Eliminated UTF-8/box-drawing and captions. Captions were cute but > > weren't "fully baked". Without them, box-drawing isn't really > > needed, and I think I prefer the ASCII look, with the actual > > "caret" character, and '~' makes it easier to count characters > > compared to a box-drawing line, in my terminal's font, at least. > > Doing so greatly simplifies the new locus-printing code. > > So I dropped the UTF-8 box drawing from that original kit for: > (a) simplicity (the original patch kit was huge in scope, covering a > bunch of ideas for diagnostics - ranges, labeling, fix-it hints, > spelling suggestions, so I wanted to reduce the scope to something > manageable) > (b) I found it easier to count characters with "~" > > > The thing I'm most nervous about with this patch is the potential for > introducing mojibake when people copy and paste GCC output. > > For example, looking at: > https://gcc.gnu.org/legacy-ml/gcc-patches/2015-03/msg00837.html > I see mojibake where the unicode line-drawing characters in my email > are being displayed in the HTML mailing list archive via "â" - > something has gone wrong with encoding somewhere between the copy&paste > from my terminal, the email, and the list archive. > > That said, looking at your email in the archive here: > https://gcc.gnu.org/pipermail/gcc-patches/2020-July/550551.html > I don't see any mojibake. > > What happens if GCC's stderr is piped into "less"? > What happens if GCC's stderr is saved in a build.log file, uploaded > somewhere, and then viewed? > etc. > > > > The elements implemented are: > > > > * Vertical lines, e.g. those indicating labels and those > > separating the > > source lines from the line numbers, are changed to line drawing > > characters. > > > > * The diagnostic paths output by the static analyzer make use of > > line > > drawing characters to output smooth corners etc. > > > > * The squiggly underline ~~~~~ used to highlight source locations > > is > > changed to a double underline ═════. The main reason for this > > is that > > it enables a seamless "tee" character to connect the underline > > to a > > label line if one exists. > > > > * Carets (^) are changed to a slightly different character (∧). I > > think > > the new one is a little nicer looking, although probably not > > worth the > > trouble on its own. I wanted to implement the support in this > > patch > > beause carets are harder to change than the rest of the > > elements > > (front ends have an interface to override them, which currently > > Fortran makes use of), so I thought it worthwhile to get this > > logic in > > place, so that it can easily be changed to a more superior > > character > > in the future if one comes up. It would also be easy enough to > > leave > > the Unicode support in place for carets, but keep the default > > set to > > the plain one for now. > > Some other ideas: > > * fix-it hints > > * maybe have a different character for separating the line numbers as > opposed to those for labels and for showing interprocedural paths. > > > As an example, this diagnostic from gcc.dg/format/diagnostic- > > ranges.c: > > > > diagnostic-ranges.c:196:28: warning: field width specifier ‘*’ > > expects argument of type ‘int’, but argument 3 has type ‘long int’ [- > > Wformat=] > > 196 | __builtin_sprintf (d, " %*ld ", foo + bar, foo); > > | ~^~~ ~~~~~~~~~ > > | | | > > | int long int > > > > would become instead: > > > > diagnostic-ranges.c:196:28: warning: field width specifier ‘*’ > > expects argument of type ‘int’, but argument 3 has type ‘long int’ [- > > Wformat=] > > 196 │ __builtin_sprintf (d, " %*ld ", foo + bar, foo); > > │ ═∧══ ════╤════ > > │ │ │ > > │ int long int > > > > Hopefully you are viewing this in a terminal that displays it > > properly :), in > > which case, hopefully you may find it to be an improvement? > > I wonder if you can upload colorized examples somewhere? > > e.g. using bin/gcc-color-to-html.py from our website repository: > https://gcc.gnu.org/git/?p=gcc-wwwdocs.git;a=blob;f=bin/gcc-color-to-html.py > or one of the various ansi2html conversion scripts e.g. > http://www.pixelbeat.org/scripts/ansi2html.sh > > > Here is a more involved example from the analyzer: > > > > setjmp-5.c: In function ‘outer’: > > setjmp-5.c:21:3: warning: ‘longjmp’ called after enclosing function > > of ‘setjmp’ has returned [-Wanalyzer-stale-setjmp-buffer] > > 21 | longjmp (env, 42); /* { dg-warning "'longjmp' called after > > enclosing function of 'setjmp' has returned" } */ > > | ^~~~~~~~~~~~~~~~~ > > ‘outer’: events 1-2 > > | > > | 15 | void outer (void) > > | | ^~~~~ > > | | | > > | | (1) entry to ‘outer’ > > |...... > > | 19 | inner (); > > | | ~~~~~~~~ > > | | | > > | | (2) calling ‘inner’ from ‘outer’ > > | > > +--> ‘inner’: event 3 > > | > > | 10 | static void inner (void) > > | | ^~~~~ > > | | | > > | | (3) entry to ‘inner’ > > | > > ‘inner’: event 4 > > | > > | 12 | SETJMP (env); > > | | ^~~~~~ > > | | | > > | | (4) ‘setjmp’ called here > > | > > <------+ > > | > > ‘outer’: events 5-6 > > | > > | 19 | inner (); > > | | ^~~~~~~~ > > | | | > > | | (5) returning to ‘outer’ from ‘inner’ > > | 20 | > > | 21 | longjmp (env, 42); /* { dg-warning "'longjmp' called > > after enclosing function of 'setjmp' has returned" } */ > > | | ~~~~~~~~~~~~~~~~~ > > | | | > > | | (6) here > > | > > > > would become instead: > > > > setjmp-5.c: In function ‘outer’: > > setjmp-5.c:21:3: warning: ‘longjmp’ called after enclosing function > > of ‘setjmp’ has returned [-Wanalyzer-stale-setjmp-buffer] > > 21 │ longjmp (env, 42); /* { dg-warning "'longjmp' called after > > enclosing function of 'setjmp' has returned" } */ > > │ ∧════════════════ > > ‘outer’: events 1-2 > > │ > > │ 15 │ void outer (void) > > │ │ ∧════ > > │ │ │ > > │ │ (1) entry to ‘outer’ > > │...... > > │ 19 │ inner (); > > I wonder if there's a fancier way to express the gap in the lines if > Unicode is available? > > > > │ │ ╤═══════ > > │ │ │ > > │ │ (2) calling ‘inner’ from ‘outer’ > > │ > > └──> ‘inner’: event 3 > > │ > > │ 10 │ static void inner (void) > > │ │ ∧════ > > │ │ │ > > │ │ (3) entry to ‘inner’ > > │ > > ‘inner’: event 4 > > │ > > │ 12 │ SETJMP (env); > > │ │ ∧═════ > > │ │ │ > > │ │ (4) ‘setjmp’ called here > > │ > > Unrelated to this patch , but it would be nice if the analyzer inserted > an event at the function end showing the frame in "env" becoming > invalid, since that's what pertinent to the diagnostic. > > > ┌<─────┘ > > │ > > ‘outer’: events 5-6 > > │ > > │ 19 │ inner (); > > │ │ ∧═══════ > > │ │ │ > > │ │ (5) returning to ‘outer’ from ‘inner’ > > │ 20 │ > > │ 21 │ longjmp (env, 42); /* { dg-warning "'longjmp' called > > after enclosing function of 'setjmp' has returned" } */ > > │ │ ╤════════════════ > > │ │ │ > > │ │ (6) here > > │ > > FWIW I experimented with using unicode circled number characters in > place of (1), (2), etc for events in diagnostic_paths but the results > looked bad in my terminal, so I stuck to the ASCII form above. > > In my more adventurous moments I've been tempted to use background > colorization to show the stack pushes and pops in a flamegraph-style > way, but I suspect it would garish and be too "busy" visually. > > > > > Although probably premature, bootstrap and regtest were done on x86- > > 64 > > linux, all tests the same before/after and new tests passing: > > FAIL 96 96 > > PASS 479090 479239 > > UNSUPPORTED 11946 11946 > > UNTESTED 194 194 > > XFAIL 1839 1839 > > XPASS 36 36 > > I see the patch kit touches Fortran; was this with all frontends > enabled? (though I guess I'm likewise being premature here) > > > I tried to set this up as a general framework, at least, it is easy > > in one > > place to change the characters that are used for various contexts, so > > that > > if people like the general idea, but not some of the specifics, the > > patch is > > easily modified for that now or in the future. Thanks for any > > feedback! > > Thanks again for the patch; let's see what others think. > Dave >
Hello- This patch to make use of Unicode drawing characters in diagnostics doesn't seem to have generated much interest; looks like there was one vote in favor. I thought I might bring it up one more time since I still think it is a potential improvement... The patch required some rebasing on top of recent commits so I have attached an updated version here. I am happy to work on it if you think it's worthwhile, also happy to drop it too. Also, David, I wanted to mention that this patch conflicts a fair amount with your recently sent HTML output patch. If it simplifies things, and you are interested in my patch, I can wait for the HTML one to be applied and then send this one relative to that. Thanks for taking a look! -Lewis
From: Lewis Hyatt <lhy...@gmail.com> Date: Wed, 11 Nov 2020 16:35:38 -0500 Subject: [PATCH] diagnostics: Add support for Unicode drawing characters Adds the new option -fdiagnostics-unicode-drawing, on by default if a UTF-8 local is detected, which modifies diagnostics to use extended Unicode characters, such as line-drawing characters. gcc/ChangeLog: * common.opt: Add new option -fdiagnostics-unicode-drawing. * diagnostic-show-locus.c (struct point_state): Add new member. (layout::print_source_line): Support Unicode drawing feature. (layout::start_annotation_line): Likewise. (layout::print_annotation_line): Likewise. (layout::print_any_labels): Likewise. (layout::print_trailing_fixits): Likewise. (layout::get_state_at_point): Likewise. (test_one_liner_multiple_carets_and_ranges): Likewise. (test_one_liner_multiple_carets_and_ranges_utf8): Likewise. * diagnostic.c (diagnostic_drawing_init): New function. (diagnostic_initialize): Call the new function. * diagnostic.h (struct diagnostic_context): Support Unicode drawing feature. (enum diagnostics_drawing_rule): New enum for the new option. (diagnostic_drawing_init): Declare. * doc/invoke.texi: Document the new option. * opts-common.c (decode_cmdline_options_to_array): Add the new option to -fdiagnostics-plain-output handling. * opts.c (common_handle_option): Call diagnostic_drawing_init() to support the new option. * selftest-diagnostic.c (test_diagnostic_context::test_diagnostic_context): Disable Unicode drawing in selftests that use test_diagnostic_context. * tree-diagnostic-path.cc (path_summary::print): Support Unicode drawing feature. gcc/fortran/ChangeLog: * error.c (gfc_diagnostics_init): Adapt custom carets, as they need to be strings rather than chars now. (gfc_diagnostics_finish): Likewise. gcc/testsuite/ChangeLog: * gcc.dg/plugin/diagnostic_plugin_test_show_locus.c: Adapt custom carets, as they need to be strings rather than chars now. * gcc.dg/analyzer/setjmp-5-utf8.c: New test. * gcc.dg/format/diagnostic-ranges-utf8.c: New test. diff --git a/gcc/common.opt b/gcc/common.opt index 7d0e0d9c88a..346001f2bde 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -1390,6 +1390,26 @@ ftabstop= Common Joined RejectNegative UInteger -ftabstop=<number> Distance between tab stops for column reporting. +fdiagnostics-unicode-drawing +Common Alias(fdiagnostics-unicode-drawing=,always,never) +; + +fdiagnostics-unicode-drawing= +Common Joined RejectNegative Enum(diagnostics_unicode_drawing) +-fdiagnostics-unicode-drawing=[never|always|auto] Use Unicode drawing characters in diagnostics. + +Enum +Name(diagnostics_unicode_drawing) Type(int) + +EnumValue +Enum(diagnostics_unicode_drawing) String(never) Value(DIAGNOSTICS_UNICODE_DRAWING_NO) + +EnumValue +Enum(diagnostics_unicode_drawing) String(always) Value(DIAGNOSTICS_UNICODE_DRAWING_YES) + +EnumValue +Enum(diagnostics_unicode_drawing) String(auto) Value(DIAGNOSTICS_UNICODE_DRAWING_AUTO) + Enum Name(diagnostic_path_format) Type(int) diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c index da3c5b6a92d..a945307a71c 100644 --- a/gcc/diagnostic-show-locus.c +++ b/gcc/diagnostic-show-locus.c @@ -62,6 +62,7 @@ struct point_state { int range_idx; bool draw_caret_p; + bool has_label_p; }; /* A class to inject colorization codes when printing the diagnostic locus. @@ -1467,7 +1468,7 @@ layout::print_source_line (linenum_type row, const char *line, int line_bytes) int width = num_digits (row); for (int i = 0; i < m_linenum_width - width; i++) pp_space (m_pp); - pp_printf (m_pp, "%i | ", row); + pp_printf (m_pp, "%i %s ", row, m_context->drawing.vertical); } else pp_space (m_pp); @@ -1596,7 +1597,8 @@ layout::start_annotation_line (char margin_char) const pp_space (m_pp); for (; i < m_linenum_width; i++) pp_character (m_pp, margin_char); - pp_string (m_pp, " |"); + pp_space (m_pp); + pp_string (m_pp, m_context->drawing.vertical); } } @@ -1628,15 +1630,21 @@ layout::print_annotation_line (linenum_type row, const line_bounds lbounds) if (state.draw_caret_p) { /* Draw the caret. */ - char caret_char; + const char *caret_char; if (state.range_idx < rich_location::STATICALLY_ALLOCATED_RANGES) caret_char = m_context->caret_chars[state.range_idx]; else - caret_char = '^'; - pp_character (m_pp, caret_char); + caret_char = m_context->drawing.default_caret; + pp_string (m_pp, caret_char); } else - pp_character (m_pp, '~'); + { + const char *const underline + = state.has_label_p + ? m_context->drawing.tee_down2 + : m_context->drawing.horizontal2; + pp_string (m_pp, underline); + } } else { @@ -1821,7 +1829,7 @@ layout::print_any_labels (linenum_type row) gcc_assert (column <= label->m_column); move_to_column (&column, label->m_column, true); m_colorizer.set_range (label->m_state_idx); - pp_character (m_pp, '|'); + pp_string (m_pp, m_context->drawing.vertical); m_colorizer.set_normal_text (); column++; } @@ -2343,7 +2351,7 @@ layout::print_trailing_fixits (linenum_type row) move_to_column (&column, start_column, true); m_colorizer.set_fixit_delete (); for (; column <= finish_column; column++) - pp_character (m_pp, '-'); + pp_string (m_pp, m_context->drawing.horizontal1); m_colorizer.set_normal_text (); } /* Print the replacement text. REPLACE also covers @@ -2400,12 +2408,19 @@ layout::get_state_at_point (/* Inputs. */ { out_state->range_idx = i; - /* Are we at the range's caret? is it visible? */ + /* If we are at the range's caret, check if it is visible and check + if a label line will be output directly below this point, since + that enables unicode drawing to use a nicer "tee" character to + mesh seamlessly with the label line. */ out_state->draw_caret_p = false; - if (range->m_range_display_kind == SHOW_RANGE_WITH_CARET - && row == range->m_caret.m_line + out_state->has_label_p = false; + if (row == range->m_caret.m_line && column == range->m_caret.m_columns[col_unit]) - out_state->draw_caret_p = true; + { + out_state->draw_caret_p + = (range->m_range_display_kind == SHOW_RANGE_WITH_CARET); + out_state->has_label_p = (range->m_label != NULL); + } /* Within a multiline range, don't display any underline in any leading or trailing whitespace on a line. @@ -2976,19 +2991,19 @@ test_one_liner_multiple_carets_and_ranges () = make_location (linemap_position_for_column (line_table, 2), linemap_position_for_column (line_table, 1), linemap_position_for_column (line_table, 3)); - dc.caret_chars[0] = 'A'; + dc.caret_chars[0] = "A"; location_t bar = make_location (linemap_position_for_column (line_table, 8), linemap_position_for_column (line_table, 7), linemap_position_for_column (line_table, 9)); - dc.caret_chars[1] = 'B'; + dc.caret_chars[1] = "B"; location_t field = make_location (linemap_position_for_column (line_table, 13), linemap_position_for_column (line_table, 11), linemap_position_for_column (line_table, 15)); - dc.caret_chars[2] = 'C'; + dc.caret_chars[2] = "C"; rich_location richloc (line_table, foo); richloc.add_range (bar, SHOW_RANGE_WITH_CARET); @@ -3584,19 +3599,19 @@ test_one_liner_multiple_carets_and_ranges_utf8 () = make_location (linemap_position_for_column (line_table, 7), linemap_position_for_column (line_table, 1), linemap_position_for_column (line_table, 8)); - dc.caret_chars[0] = 'A'; + dc.caret_chars[0] = "A"; location_t bar = make_location (linemap_position_for_column (line_table, 16), linemap_position_for_column (line_table, 12), linemap_position_for_column (line_table, 17)); - dc.caret_chars[1] = 'B'; + dc.caret_chars[1] = "B"; location_t field = make_location (linemap_position_for_column (line_table, 26), linemap_position_for_column (line_table, 19), linemap_position_for_column (line_table, 30)); - dc.caret_chars[2] = 'C'; + dc.caret_chars[2] = "C"; rich_location richloc (line_table, foo); richloc.add_range (bar, SHOW_RANGE_WITH_CARET); richloc.add_range (field, SHOW_RANGE_WITH_CARET); diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c index 1b6c9845892..131b7220dcb 100644 --- a/gcc/diagnostic.c +++ b/gcc/diagnostic.c @@ -167,6 +167,14 @@ default_diagnostic_final_cb (diagnostic_context *context) } } +/* Store the carets in an unnamed namespace so we can verify whether + or not a front-end has overriden them. */ +namespace { + const char plain_caret[] = "^"; + /* U+2227 = Logical And */ + const char fancy_caret[] = "\xE2\x88\xA7"; +} + /* Initialize the diagnostic message outputting machinery. */ void diagnostic_initialize (diagnostic_context *context, int n_opts) @@ -187,7 +195,7 @@ diagnostic_initialize (diagnostic_context *context, int n_opts) context->show_caret = false; diagnostic_set_caret_max_width (context, pp_line_cutoff (context->printer)); for (i = 0; i < rich_location::STATICALLY_ALLOCATED_RANGES; i++) - context->caret_chars[i] = '^'; + context->caret_chars[i] = plain_caret; context->show_cwe = false; context->path_format = DPF_NONE; context->show_path_depths = false; @@ -229,6 +237,7 @@ diagnostic_initialize (diagnostic_context *context, int n_opts) context->begin_group_cb = NULL; context->end_group_cb = NULL; context->final_cb = default_diagnostic_final_cb; + diagnostic_drawing_init (context, DIAGNOSTICS_UNICODE_DRAWING_AUTO); } /* Maybe initialize the color support. We require clients to do this @@ -286,6 +295,83 @@ diagnostic_urls_init (diagnostic_context *context, int value /*= -1 */) = determine_url_format ((diagnostic_url_rule_t) value); } +/* Initialize unicode drawing support in CONTEXT if requested, or, in auto + mode, if the locale supports it. */ +void +diagnostic_drawing_init (diagnostic_context *context, + enum diagnostics_drawing_rule rule) +{ + switch (rule) + { + case DIAGNOSTICS_UNICODE_DRAWING_NO: + context->drawing.enabled = false; + break; + case DIAGNOSTICS_UNICODE_DRAWING_YES: + context->drawing.enabled = true; + break; + case DIAGNOSTICS_UNICODE_DRAWING_AUTO: + context->drawing.enabled = locale_utf8; + break; + default: + gcc_unreachable (); + } + if (context->drawing.enabled) + { + /* U+2500 = Box Drawings Light Horizontal */ + context->drawing.horizontal1 = "\xE2\x94\x80"; + + /* U+2550 = Box Drawings Double Horizontal */ + context->drawing.horizontal2 = "\xE2\x95\x90"; + + /* U+252C = Box Drawings Light Down and Horizontal */ + context->drawing.tee_down1 = "\xE2\x94\xAC"; + + /* U+2564 = Box Drawings Down Single and Horizontal Double */ + context->drawing.tee_down2 = "\xE2\x95\xA4"; + + /* U+2502 = Box Drawings Light Vertical */ + context->drawing.vertical = "\xE2\x94\x82"; + + /* U+250C = Box Drawings Light Down and Right */ + context->drawing.corner_nw = "\xE2\x94\x8C"; + + /* U+2510 = Box Drawings Light Down and Left */ + context->drawing.corner_ne = "\xE2\x94\x90"; + + /* U+2518 = Box Drawings Light Up and Left */ + context->drawing.corner_se = "\xE2\x94\x98"; + + /* U+2514 = Box Drawings Light Up and Right */ + context->drawing.corner_sw = "\xE2\x94\x94"; + + context->drawing.default_caret = fancy_caret; + } + else + { + context->drawing.horizontal1 = "-"; + context->drawing.tee_down1 = "-"; + context->drawing.horizontal2 = "~"; + context->drawing.tee_down2 = "~"; + context->drawing.vertical = "|"; + context->drawing.corner_nw = "+"; + context->drawing.corner_ne = "+"; + context->drawing.corner_se = "+"; + context->drawing.corner_sw = "+"; + context->drawing.default_caret = plain_caret; + } + + /* Configure the customizable carets, unless a front-end has already changed + them to something else. */ + for (int i = 0; i != rich_location::STATICALLY_ALLOCATED_RANGES; ++i) + { + if (context->caret_chars[i] == plain_caret + || context->caret_chars[i] == fancy_caret) + { + context->caret_chars[i] = context->drawing.default_caret; + } + } +} + /* Do any cleaning up required after the last diagnostic is emitted. */ void diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h index 4051601abfd..329ed555139 100644 --- a/gcc/diagnostic.h +++ b/gcc/diagnostic.h @@ -159,8 +159,9 @@ struct diagnostic_context /* Maximum width of the source line printed. */ int caret_max_width; - /* Character used for caret diagnostics. */ - char caret_chars[rich_location::STATICALLY_ALLOCATED_RANGES]; + /* Character used for caret diagnostics. These are strings to accommodate + multibyte characters, but they should have display width 1. */ + const char *caret_chars[rich_location::STATICALLY_ALLOCATED_RANGES]; /* True if we should print any CWE identifiers associated with diagnostics. */ @@ -326,6 +327,23 @@ struct diagnostic_context /* Callback for final cleanup. */ void (*final_cb) (diagnostic_context *context); + + /* Some output elements that can be made to look nicer if UTF-8 output + is available. */ + struct + { + bool enabled; + const char *horizontal1; + const char *horizontal2; + const char *tee_down1; + const char *tee_down2; + const char *vertical; + const char *corner_nw; + const char *corner_ne; + const char *corner_se; + const char *corner_sw; + const char *default_caret; + } drawing; }; static inline void @@ -396,6 +414,16 @@ diagnostic_override_option_index (diagnostic_info *info, int optidx) extern void diagnostic_initialize (diagnostic_context *, int); extern void diagnostic_color_init (diagnostic_context *, int value = -1); extern void diagnostic_urls_init (diagnostic_context *, int value = -1); + +enum diagnostics_drawing_rule +{ + DIAGNOSTICS_UNICODE_DRAWING_NO, + DIAGNOSTICS_UNICODE_DRAWING_YES, + DIAGNOSTICS_UNICODE_DRAWING_AUTO +}; +extern void diagnostic_drawing_init (diagnostic_context *, + enum diagnostics_drawing_rule rule); + extern void diagnostic_finish (diagnostic_context *); extern void diagnostic_report_current_module (diagnostic_context *, location_t); extern void diagnostic_show_locus (diagnostic_context *, diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 8d0d2136831..24eadd2ec64 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -286,6 +286,7 @@ Objective-C and Objective-C++ Dialects}. -fdiagnostics-plain-output @gol -fdiagnostics-show-location=@r{[}once@r{|}every-line@r{]} @gol -fdiagnostics-color=@r{[}auto@r{|}never@r{|}always@r{]} @gol +-fdiagnostics-unicode-drawing=@r{[}auto@r{|}never@r{|}always@r{]} @gol -fdiagnostics-urls=@r{[}auto@r{|}never@r{|}always@r{]} @gol -fdiagnostics-format=@r{[}text@r{|}json@r{]} @gol -fno-diagnostics-show-option -fno-diagnostics-show-caret @gol @@ -4425,7 +4426,8 @@ options: -fno-diagnostics-show-line-numbers @gol -fdiagnostics-color=never @gol -fdiagnostics-urls=never @gol --fdiagnostics-path-format=separate-events} +-fdiagnostics-path-format=separate-events @gol +-fdiagnostics-unicode-drawing=never} In the future, if GCC changes the default appearance of its diagnostics, the corresponding option to disable the new behavior will be added to this list. @@ -4565,6 +4567,16 @@ SGR substring for highlighting mismatching types within template arguments in the C++ frontend. @end table +@item -fdiagnostics-unicode-drawing[=@var{WHEN}] +@opindex fdiagnostics-unicode-drawing +@opindex fno-diagnostics-unicode-drawing +Use extended characters from the Unicode Standard (such as line drawing +characters) to improve the appearance of diagnostics. + +@var{WHEN} is @samp{never}, @samp{always}, or @samp{auto}. +@samp{auto}, the default, enables the feature provided the user's locale +supports UTF-8. + @item -fdiagnostics-urls[=@var{WHEN}] @opindex fdiagnostics-urls @cindex urls diff --git a/gcc/fortran/error.c b/gcc/fortran/error.c index dacc1d7ba51..68be60309ce 100644 --- a/gcc/fortran/error.c +++ b/gcc/fortran/error.c @@ -1532,8 +1532,8 @@ gfc_diagnostics_init (void) global_dc->start_span = gfc_diagnostic_start_span; diagnostic_finalizer (global_dc) = gfc_diagnostic_finalizer; diagnostic_format_decoder (global_dc) = gfc_format_decoder; - global_dc->caret_chars[0] = '1'; - global_dc->caret_chars[1] = '2'; + global_dc->caret_chars[0] = "1"; + global_dc->caret_chars[1] = "2"; pp_warning_buffer = new (XNEW (output_buffer)) output_buffer (); pp_warning_buffer->flush_p = false; /* pp_error_buffer is statically allocated. This simplifies memory @@ -1550,6 +1550,6 @@ gfc_diagnostics_finish (void) defaults. */ diagnostic_starter (global_dc) = gfc_diagnostic_starter; diagnostic_finalizer (global_dc) = gfc_diagnostic_finalizer; - global_dc->caret_chars[0] = '^'; - global_dc->caret_chars[1] = '^'; + global_dc->caret_chars[0] = global_dc->drawing.default_caret; + global_dc->caret_chars[1] = global_dc->drawing.default_caret; } diff --git a/gcc/opts-common.c b/gcc/opts-common.c index 8ec8c1ec1a8..c13904ebf50 100644 --- a/gcc/opts-common.c +++ b/gcc/opts-common.c @@ -1001,6 +1001,7 @@ decode_cmdline_options_to_array (unsigned int argc, const char **argv, "-fdiagnostics-color=never", "-fdiagnostics-urls=never", "-fdiagnostics-path-format=separate-events", + "-fdiagnostics-unicode-drawing=never", }; const int num_expanded = ARRAY_SIZE (expanded_args); opt_array_len += num_expanded - 1; diff --git a/gcc/opts.c b/gcc/opts.c index ac9972d9c38..24a2058f45a 100644 --- a/gcc/opts.c +++ b/gcc/opts.c @@ -2449,6 +2449,10 @@ common_handle_option (struct gcc_options *opts, diagnostic_urls_init (dc, value); break; + case OPT_fdiagnostics_unicode_drawing_: + diagnostic_drawing_init (dc, (enum diagnostics_drawing_rule)value); + break; + case OPT_fdiagnostics_format_: diagnostic_output_format_init (dc, (enum diagnostics_output_format)value); diff --git a/gcc/selftest-diagnostic.c b/gcc/selftest-diagnostic.c index 82fddca89ab..3bce8e58048 100644 --- a/gcc/selftest-diagnostic.c +++ b/gcc/selftest-diagnostic.c @@ -36,6 +36,10 @@ namespace selftest { test_diagnostic_context::test_diagnostic_context () { diagnostic_initialize (this, 0); + + /* Disable unicode drawing to make it simpler to write selftest code. */ + diagnostic_drawing_init (this, DIAGNOSTICS_UNICODE_DRAWING_NO); + show_caret = true; show_labels_p = true; show_column = true; diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-5-utf8.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-5-utf8.c new file mode 100644 index 00000000000..6f65cf9a2da --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-5-utf8.c @@ -0,0 +1,71 @@ +/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret -fdiagnostics-unicode-drawing" } */ + +/* This tests the same functionality as setjmp-5.c, but enables unicode drawing + so as to exercise those features. + n.b. NN line numbers do not support unicode drawing mode, so we do not + make use of that here. */ + +#include "test-setjmp.h" +#include <stddef.h> +#include "analyzer-decls.h" + +static jmp_buf env; + +static void inner (void) +{ + SETJMP (env); +} + +void outer (void) +{ + int i; + + inner (); + + longjmp (env, 42); /* { dg-warning "'longjmp' called after enclosing function of 'setjmp' has returned" } */ +} + +/* { dg-begin-multiline-output "" } + 25 │ longjmp (env, 42); + │ ∧════════════════ + 'outer': events 1-2 + │ + │ 19 │ void outer (void) + │ │ ∧════ + │ │ │ + │ │ (1) entry to 'outer' + │...... + │ 23 │ inner (); + │ │ ╤═══════ + │ │ │ + │ │ (2) calling 'inner' from 'outer' + │ + └──> 'inner': event 3 + │ + │ 14 │ static void inner (void) + │ │ ∧════ + │ │ │ + │ │ (3) entry to 'inner' + │ + 'inner': event 4 + │ + │ 16 │ SETJMP (env); + │ │ ∧═════ + │ │ │ + │ │ (4) 'setjmp' called here + │ + ┌<─────┘ + │ + 'outer': events 5-6 + │ + │ 23 │ inner (); + │ │ ∧═══════ + │ │ │ + │ │ (5) returning to 'outer' from 'inner' + │ 24 │ + │ 25 │ longjmp (env, 42); + │ │ ╤════════════════ + │ │ │ + │ │ (6) here + │ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/format/diagnostic-ranges-utf8.c b/gcc/testsuite/gcc.dg/format/diagnostic-ranges-utf8.c new file mode 100644 index 00000000000..ac7638e003a --- /dev/null +++ b/gcc/testsuite/gcc.dg/format/diagnostic-ranges-utf8.c @@ -0,0 +1,394 @@ +/* { dg-options "-Wformat -fdiagnostics-show-caret -fdiagnostics-unicode-drawing" } */ + +/* This performs the same tests as diagnostic-ranges.c, but it enables unicode + drawing so that this can be tested. */ + + +#include "format.h" + +void test_mismatching_types (const char *msg) +{ + printf("hello %i", msg); /* { dg-warning "format '%i' expects argument of type 'int', but argument 2 has type 'const char \\*' " } */ + +/* { dg-begin-multiline-output "" } + printf("hello %i", msg); + ═∧ ╤══ + │ │ + int const char * + %s + { dg-end-multiline-output "" } */ + + + printf("hello %s", 42); /* { dg-warning "format '%s' expects argument of type 'char \\*', but argument 2 has type 'int'" } */ +/* { dg-begin-multiline-output "" } + printf("hello %s", 42); + ═∧ ╤═ + │ │ + │ int + char * + %d + { dg-end-multiline-output "" } */ + + printf("hello %i", (long)0); /* { dg-warning "format '%i' expects argument of type 'int', but argument 2 has type 'long int' " } */ +/* { dg-begin-multiline-output "" } + printf("hello %i", (long)0); + ═∧ ╤══════ + │ │ + int long int + %li + { dg-end-multiline-output "" } */ +} + +void test_multiple_arguments (void) +{ + printf ("arg0: %i arg1: %s arg 2: %i", /* { dg-warning "29: format '%s'" } */ + 100, 101, 102); +/* { dg-begin-multiline-output "" } + printf ("arg0: %i arg1: %s arg 2: %i", + ═∧ + │ + char * + %d + 100, 101, 102); + ╤══ + │ + int + { dg-end-multiline-output "" } */ +} + +void test_multiple_arguments_2 (int i, int j) +{ + printf ("arg0: %i arg1: %s arg 2: %i", /* { dg-warning "29: format '%s'" } */ + 100, i + j, 102); +/* { dg-begin-multiline-output "" } + printf ("arg0: %i arg1: %s arg 2: %i", + ═∧ + │ + char * + %d + 100, i + j, 102); + ══╤══ + │ + int + { dg-end-multiline-output "" } */ +} + +void multiline_format_string (void) { + printf ("before the fmt specifier" /* { dg-warning "11: format '%d' expects a matching 'int' argument" } */ +/* { dg-begin-multiline-output "" } + printf ("before the fmt specifier" + ∧═════════════════════════ + { dg-end-multiline-output "" } */ + + "%" + "d" /* { dg-message "12: format string is defined here" } */ + "after the fmt specifier"); + +/* { dg-begin-multiline-output "" } + "%" + ══ + "d" + ═∧ + │ + int + { dg-end-multiline-output "" } */ +} + +void test_hex (const char *msg) +{ + /* "%" is \x25 + "i" is \x69 */ + printf("hello \x25\x69", msg); /* { dg-warning "format '%i' expects argument of type 'int', but argument 2 has type 'const char \\*' " } */ + +/* { dg-begin-multiline-output "" } + printf("hello \x25\x69", msg); + ════∧═══ ╤══ + │ │ + int const char * + \x25s + { dg-end-multiline-output "" } */ +} + +void test_oct (const char *msg) +{ + /* "%" is octal 045 + "i" is octal 151. */ + printf("hello \045\151", msg); /* { dg-warning "format '%i' expects argument of type 'int', but argument 2 has type 'const char \\*' " } */ + +/* { dg-begin-multiline-output "" } + printf("hello \045\151", msg); + ════∧═══ ╤══ + │ │ + int const char * + \045s + { dg-end-multiline-output "" } */ +} + +void test_multiple (const char *msg) +{ + /* "%" is \x25 in hex + "i" is \151 in octal. */ + printf("prefix" "\x25" "\151" "suffix", /* { dg-warning "format '%i'" } */ + msg); +/* { dg-begin-multiline-output "" } + printf("prefix" "\x25" "\151" "suffix", + ∧═══════ + msg); + ╤══ + │ + const char * + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + printf("prefix" "\x25" "\151" "suffix", + ════════∧═══ + │ + int + \x25" "s + { dg-end-multiline-output "" } */ +} + +void test_u8 (const char *msg) +{ + printf(u8"hello %i", msg);/* { dg-warning "format '%i' expects argument of type 'int', but argument 2 has type 'const char \\*' " } */ +/* { dg-begin-multiline-output "" } + printf(u8"hello %i", msg); + ═∧ ╤══ + │ │ + int const char * + %s + { dg-end-multiline-output "" } */ +} + +void test_param (long long_i, long long_j) +{ + printf ("foo %s bar", long_i + long_j); /* { dg-warning "17: format '%s' expects argument of type 'char \\*', but argument 2 has type 'long int'" } */ +/* { dg-begin-multiline-output "" } + printf ("foo %s bar", long_i + long_j); + ═∧ ═══════╤═══════ + │ │ + char * long int + %ld + { dg-end-multiline-output "" } */ +} + +void test_field_width_specifier (long l, int i1, int i2) +{ + printf (" %*.*d ", l, i1, i2); /* { dg-warning "14: field width specifier '\\*' expects argument of type 'int', but argument 2 has type 'long int'" } */ +/* { dg-begin-multiline-output "" } + printf (" %*.*d ", l, i1, i2); + ═∧═══ ╤ + │ │ + int long int + { dg-end-multiline-output "" } */ +} + +/* PR c/72857. */ + +void test_field_width_specifier_2 (char *d, long foo, long bar) +{ + __builtin_sprintf (d, " %*ld ", foo, foo); /* { dg-warning "28: field width specifier '\\*' expects argument of type 'int', but argument 3 has type 'long int'" } */ + /* { dg-begin-multiline-output "" } + __builtin_sprintf (d, " %*ld ", foo, foo); + ═∧══ ╤══ + │ │ + int long int + { dg-end-multiline-output "" } */ + + __builtin_sprintf (d, " %*ld ", foo + bar, foo); /* { dg-warning "28: field width specifier '\\*' expects argument of type 'int', but argument 3 has type 'long int'" } */ + /* { dg-begin-multiline-output "" } + __builtin_sprintf (d, " %*ld ", foo + bar, foo); + ═∧══ ════╤════ + │ │ + int long int + { dg-end-multiline-output "" } */ +} + +void test_field_precision_specifier (char *d, long foo, long bar) +{ + __builtin_sprintf (d, " %.*ld ", foo, foo); /* { dg-warning "29: field precision specifier '\\.\\*' expects argument of type 'int', but argument 3 has type 'long int'" } */ + /* { dg-begin-multiline-output "" } + __builtin_sprintf (d, " %.*ld ", foo, foo); + ══∧══ ╤══ + │ │ + int long int + { dg-end-multiline-output "" } */ + + __builtin_sprintf (d, " %.*ld ", foo + bar, foo); /* { dg-warning "29: field precision specifier '\\.\\*' expects argument of type 'int', but argument 3 has type 'long int'" } */ + /* { dg-begin-multiline-output "" } + __builtin_sprintf (d, " %.*ld ", foo + bar, foo); + ══∧══ ════╤════ + │ │ + int long int + { dg-end-multiline-output "" } */ +} + +void test_spurious_percent (void) +{ + printf("hello world %"); /* { dg-warning "23: spurious trailing" } */ + +/* { dg-begin-multiline-output "" } + printf("hello world %"); + ∧ + { dg-end-multiline-output "" } */ +} + +void test_empty_precision (char *s, size_t m, double d) +{ + strfmon (s, m, "%#.5n", d); /* { dg-warning "20: empty left precision in gnu_strfmon format" } */ +/* { dg-begin-multiline-output "" } + strfmon (s, m, "%#.5n", d); + ∧ + { dg-end-multiline-output "" } */ + + strfmon (s, m, "%#5.n", d); /* { dg-warning "22: empty precision in gnu_strfmon format" } */ +/* { dg-begin-multiline-output "" } + strfmon (s, m, "%#5.n", d); + ∧ + { dg-end-multiline-output "" } */ +} + +void test_repeated (int i) +{ + printf ("%++d", i); /* { dg-warning "14: repeated '\\+' flag in format" } */ +/* { dg-begin-multiline-output "" } + printf ("%++d", i); + ∧ + { dg-end-multiline-output "" } */ +} + +void test_conversion_lacks_type (void) +{ + printf (" %h"); /* { dg-warning "14:conversion lacks type at end of format" } */ +/* { dg-begin-multiline-output "" } + printf (" %h"); + ∧ + { dg-end-multiline-output "" } */ +} + +void test_embedded_nul (void) +{ + printf (" \0 "); /* { dg-warning "13:embedded" "warning for embedded NUL" } */ +/* { dg-begin-multiline-output "" } + printf (" \0 "); + ∧═ + { dg-end-multiline-output "" } */ +} + +void test_macro (const char *msg) +{ +#define INT_FMT "%i" /* { dg-message "19: format string is defined here" } */ + printf("hello " INT_FMT " world", msg); /* { dg-warning "10: format '%i' expects argument of type 'int', but argument 2 has type 'const char \\*' " } */ +/* { dg-begin-multiline-output "" } + printf("hello " INT_FMT " world", msg); + ∧═══════ ╤══ + │ + const char * + { dg-end-multiline-output "" } */ +/* { dg-begin-multiline-output "" } + #define INT_FMT "%i" + ═∧ + │ + int + %s + { dg-end-multiline-output "" } */ +#undef INT_FMT +} + +void test_macro_2 (const char *msg) +{ +#define PRIu32 "u" /* { dg-message "17: format string is defined here" } */ + printf("hello %" PRIu32 " world", msg); /* { dg-warning "10: format '%u' expects argument of type 'unsigned int', but argument 2 has type 'const char \\*' " } */ +/* { dg-begin-multiline-output "" } + printf("hello %" PRIu32 " world", msg); + ∧════════ ╤══ + │ + const char * + { dg-end-multiline-output "" } */ +/* { dg-begin-multiline-output "" } + #define PRIu32 "u" + ∧ + │ + unsigned int + { dg-end-multiline-output "" } */ +#undef PRIu32 +} + +void test_macro_3 (const char *msg) +{ +#define FMT_STRING "hello %i world" /* { dg-line test_macro_3_macro_line } */ + /* { dg-warning "20: format '%i' expects argument of type 'int', but argument 2 has type 'const char \\*'" "" { target *-*-*} .-1 } */ + printf(FMT_STRING, msg); /* { dg-message "10: in expansion of macro 'FMT_STRING" } */ +/* { dg-begin-multiline-output "" } + #define FMT_STRING "hello %i world" + ∧═══════════════ + { dg-end-multiline-output "" } */ +/* { dg-begin-multiline-output "" } + printf(FMT_STRING, msg); + ∧═════════ + { dg-end-multiline-output "" } */ +/* { dg-message "28: format string is defined here" "" { target *-*-* } test_macro_3_macro_line } */ +/* { dg-begin-multiline-output "" } + #define FMT_STRING "hello %i world" + ═∧ + │ + int + %s + { dg-end-multiline-output "" } */ +#undef FMT_STRING +} + +void test_macro_4 (const char *msg) +{ +#define FMT_STRING "hello %i world" /* { dg-warning "20: format '%i' expects argument of type 'int', but argument 2 has type 'const char \\*' " } */ + printf(FMT_STRING "\n", msg); /* { dg-message "10: in expansion of macro 'FMT_STRING" } */ +/* { dg-begin-multiline-output "" } + #define FMT_STRING "hello %i world" + ∧═══════════════ + { dg-end-multiline-output "" } */ +/* { dg-begin-multiline-output "" } + printf(FMT_STRING "\n", msg); + ∧═════════ + { dg-end-multiline-output "" } */ +/* { dg-begin-multiline-output "" } + #define FMT_STRING "hello %i world" + ═∧ + │ + int + %s + { dg-end-multiline-output "" } */ +#undef FMT_STRING +} + +void test_non_contiguous_strings (void) +{ + __builtin_printf(" %" "d ", 0.5); /* { dg-warning "20: format .%d. expects argument of type .int., but argument 2 has type .double." } */ + /* { dg-message "26: format string is defined here" "" { target *-*-* } .-1 } */ + /* { dg-begin-multiline-output "" } + __builtin_printf(" %" "d ", 0.5); + ∧═══ ╤══ + │ + double + { dg-end-multiline-output "" } */ + /* { dg-begin-multiline-output "" } + __builtin_printf(" %" "d ", 0.5); + ════∧ + │ + int + %" "f + { dg-end-multiline-output "" } */ +} + +void test_const_arrays (void) +{ + /* TODO: ideally we'd highlight both the format string *and* the use of + it here. For now, just verify that we gracefully handle this case. */ + const char a[] = " %d "; + __builtin_printf(a, 0.5); /* { dg-warning "20: format .%d. expects argument of type .int., but argument 2 has type .double." } */ + /* { dg-begin-multiline-output "" } + __builtin_printf(a, 0.5); + ∧ ╤══ + │ + double + { dg-end-multiline-output "" } */ +} diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.c index 482dbda47f7..e662ec43347 100644 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.c +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.c @@ -289,11 +289,11 @@ test_show_locus (function *fun) location_t caret_b = get_loc (line, 11); rich_location richloc (line_table, caret_a); add_range (&richloc, caret_b, caret_b, SHOW_RANGE_WITH_CARET); - global_dc->caret_chars[0] = 'A'; - global_dc->caret_chars[1] = 'B'; + global_dc->caret_chars[0] = "A"; + global_dc->caret_chars[1] = "B"; warning_at (&richloc, 0, "test"); - global_dc->caret_chars[0] = '^'; - global_dc->caret_chars[1] = '^'; + global_dc->caret_chars[0] = global_dc->drawing.default_caret; + global_dc->caret_chars[1] = global_dc->drawing.default_caret; } /* Tests of rendering fixit hints. */ @@ -407,11 +407,11 @@ test_show_locus (function *fun) location_t caret_b = get_loc (line - 1, 19); rich_location richloc (line_table, caret_a); richloc.add_range (caret_b, SHOW_RANGE_WITH_CARET); - global_dc->caret_chars[0] = '1'; - global_dc->caret_chars[1] = '2'; + global_dc->caret_chars[0] = "1"; + global_dc->caret_chars[1] = "2"; warning_at (&richloc, 0, "test"); - global_dc->caret_chars[0] = '^'; - global_dc->caret_chars[1] = '^'; + global_dc->caret_chars[0] = global_dc->drawing.default_caret; + global_dc->caret_chars[1] = global_dc->drawing.default_caret; } /* Example of using the "%q+D" format code, which as well as printing diff --git a/gcc/tree-diagnostic-path.cc b/gcc/tree-diagnostic-path.cc index 164df86037e..293d9ffd10d 100644 --- a/gcc/tree-diagnostic-path.cc +++ b/gcc/tree-diagnostic-path.cc @@ -328,11 +328,13 @@ print_path_summary_as_text (const path_summary *ps, diagnostic_context *dc, if (range->m_stack_depth > prev_range->m_stack_depth) { /* Show pushed stack frame(s). */ - const char *push_prefix = "+--> "; pp_string (pp, start_line_color); - pp_string (pp, push_prefix); + pp_string (pp, dc->drawing.corner_sw); + pp_string (pp, dc->drawing.horizontal1); + pp_string (pp, dc->drawing.horizontal1); + pp_string (pp, "> "); + cur_indent += 5; pp_string (pp, end_line_color); - cur_indent += strlen (push_prefix); } } if (range->m_fndecl) @@ -354,7 +356,7 @@ print_path_summary_as_text (const path_summary *ps, diagnostic_context *dc, { write_indent (pp, cur_indent + per_frame_indent); pp_string (pp, start_line_color); - pp_string (pp, "|"); + pp_string (pp, dc->drawing.vertical); pp_string (pp, end_line_color); pp_newline (pp); @@ -364,7 +366,7 @@ print_path_summary_as_text (const path_summary *ps, diagnostic_context *dc, pretty_printer tmp_pp; write_indent (&tmp_pp, cur_indent + per_frame_indent); pp_string (&tmp_pp, start_line_color); - pp_string (&tmp_pp, "|"); + pp_string (&tmp_pp, dc->drawing.vertical); pp_string (&tmp_pp, end_line_color); prefix = xstrdup (pp_formatted_text (&tmp_pp)); } @@ -375,7 +377,7 @@ print_path_summary_as_text (const path_summary *ps, diagnostic_context *dc, write_indent (pp, cur_indent + per_frame_indent); pp_string (pp, start_line_color); - pp_string (pp, "|"); + pp_string (pp, dc->drawing.vertical); pp_string (pp, end_line_color); pp_newline (pp); } @@ -400,18 +402,23 @@ print_path_summary_as_text (const path_summary *ps, diagnostic_context *dc, = vbar_for_next_frame - per_frame_indent; write_indent (pp, vbar_for_next_frame); pp_string (pp, start_line_color); + int col = indent_for_next_frame + per_frame_indent; + if (dc->drawing.enabled) + { + pp_string (pp, dc->drawing.corner_nw); + ++col; + } pp_character (pp, '<'); - for (int i = indent_for_next_frame + per_frame_indent; - i < cur_indent + per_frame_indent - 1; i++) - pp_character (pp, '-'); - pp_character (pp, '+'); + for (; col < cur_indent + per_frame_indent - 1; col++) + pp_string (pp, dc->drawing.horizontal1); + pp_string (pp, dc->drawing.corner_se); pp_string (pp, end_line_color); pp_newline (pp); cur_indent = indent_for_next_frame; write_indent (pp, vbar_for_next_frame); pp_string (pp, start_line_color); - pp_printf (pp, "|"); + pp_printf (pp, dc->drawing.vertical); pp_string (pp, end_line_color); pp_newline (pp); }