This patch reworks the HTML output from the the option
-fdiagnostics-add-output=experimental-html
so that for source quoting and path printing, rather than simply adding
the textual output inside <pre> elements, it breaks up the output
into HTML tags reflecting the structure of the output, using CSS, SVG
and a little javascript to help the user navigate the diagnostics and
the events within any paths.
This uses ideas from the patch I posted in:
https://gcc.gnu.org/pipermail/gcc-patches/2020-November/558603.html
but reworks the above patch so that:
* rather than printing source to a pretty_printer, the HTML is created
by building a DOM tree in memory, using a new xml::printer class. This
should be less error-prone than the pretty_printer approach, since it
ought to solve escaping issues. Instead of a text vs html boolean,
the code is generalized via templates with to_text vs to_html sinks.
This templatization applies both to path-printing and
diagnostic-show-locus.cc.
* the HTML output can have multiple diagnostics and multiple paths rather
than just a single path. The javascript keyboard controls now cycle
through all diagnostics and all events within them
An example of the output can be seen at:
https://dmalcolm.fedorapeople.org/gcc/2025-05-27/malloc-1.c.html
where the keys "j" and "k" cycle through diagnostics and events
within them.
gcc/ChangeLog:
PR other/116792
* diagnostic-format-html.cc: Define INCLUDE_STRING.
Include "xml.h", "xml-printer.h", and "json.h".
(html_generation_options::html_generation_options): New.
(namespace xml): Move decls to xml.h and convert from using
label_text to std::string.
(xml::text::write_as_xml): Reimplement indentation so it is done
by this node, rather than the parent.
(xml::node_with_children::add_text): Convert from label_text to
std::string. Consolidate runs of text into a single node.
(xml::document::write_as_xml): Reimplement indentation.
(xml::element::write_as_xml): Reimplement indentation so it is
done by this node, rather than the parent. Convert from
label_text to std::string. Update attribute-printing to new
representation to preserve insertion order.
(xml::element::set_attr): Convert from label_text to std::string.
Record insertion order.
(xml::raw::write_as_xml): New.
(xml::printer::printer): New.
(xml::printer::push_tag): New.
(xml::printer::push_tag_with_class): New.
(xml::printer::pop_tag): New.
(xml::printer::set_attr): New.
(xml::printer::add_text): New.
(xml::printer::add_raw): New.
(xml::printer::push_element): New.
(xml::printer::append): New.
(xml::printer::get_insertion_point): New.
(html_builder::add_focus_id): New.
(html_builder::m_html_gen_opts): New field.
(html_builder::m_head_element): New field.
(html_builder::m_next_diag_id): New field.
(html_builder::m_ui_focus_ids): New field.
(make_div): Convert from label_text to std::string.
(make_span): Likewise.
(HTML_STYLE): New.
(HTML_SCRIPT): New.
(html_builder::html_builder): Fix indentation. Add
"html_gen_opts" param. Initialize new fields. Reimplement
using xml::printer. Optionally add style and script tags.
(class html_path_label_writer): New.
(html_builder::make_element_for_diagnostic): Convert from
label_text to std::string. Set "id" on "gcc-diagnostic" and
"gcc-message" <div> elements; add the latter to the focus ids.
Use diagnostic_context::maybe_show_locus_as_html rather than
html_builder::make_element_for_source. Use print_path_as_html
rather than html_builder::make_element_for_path.
(html_builder::make_element_for_source): Drop.
(html_builder::make_element_for_path): Drop.
(html_builder::make_element_for_patch): Convert from label_text to
std::string.
(html_builder::make_metadata_element): Likewise. Use
xml::printer.
(html_builder::make_element_for_metadata): Convert from label_text
to std::string.
(html_builder::emit_diagram): Expand comment.
(html_builder::flush_to_file): Write out initializer for
"focus_ids" into javascript.
(html_output_format::html_output_format): Add param
"html_gen_opts" and use it to initialize m_builder.
(html_file_output_format::html_file_output_format): Likewise, to
initialize base class.
(make_html_sink): Likewise, to pass to ctor.
(selftest::test_html_diagnostic_context::test_html_diagnostic_context):
Set up html_generation_options.
(selftest::html_buffered_output_format::html_buffered_output_format):
Add html_gen_opts param.
(selftest::test_simple_log): Add id attributes to expected text
for "gcc-diagnostic" and "gcc-message" elements. Update
whitespace for indentation fixes.
(selftest::test_metadata): Update whitespace for indentation
fixes.
(selftest::test_printer): New selftest.
(selftest::test_attribute_ordering): New selftest.
(selftest::diagnostic_format_html_cc_tests): Call the new
selftests.
* diagnostic-format-html.h (struct html_generation_options): New.
(make_html_sink): Add "html_gen_opts" param.
(print_path_as_html): New decl.
* diagnostic-path-output.cc: Define INCLUDE_MAP. Add includes of
"diagnostic-format-html.h", "xml.h", and "xml-printer.h".
(path_print_policy::path_print_policy): Add ctor.
(path_print_policy::get_diagram_theme): Fix whitespace.
(struct stack_frame): New.
(begin_html_stack_frame): New function.
(end_html_stack_frame): New function.
(emit_svg_arrow): New function.
(event_range::print): Rename to...
(event_range::print_as_text): ...this. Update call to
diagnostic_start_span.
(event_range::print_as_html): New, based on the above, but ported
from pretty_printer to xml::printer.
(thread_event_printer::print_swimlane_for_event_range): Rename
to...
(thread_event_printer::print_swimlane_for_event_range_as_text):
...this. Update for renaming of event_range::print to
event_range::print_as_text.
(thread_event_printer::print_swimlane_for_event_range_as_html):
New.
(print_path_summary_as_text): Update for "_as_text" renaming.
(print_path_summary_as_html): New.
(print_path_as_html): New.
* diagnostic-show-locus.cc: Add defines of INCLUDE_MAP and
INCLUDE_STRING. Add includes of "xml.h" and "xml-printer.h".
(struct char_display_policy): Replace "m_print_cb" with
"m_print_text_cb" and "m_print_html_cb".
(struct to_text): New.
(struct to_html): New.
(get_printer): New.
(default_diagnostic_start_span_fn<to_text>): New.
(default_diagnostic_start_span_fn<to_html>): New.
(class layout): Update "friend class layout_printer;" for
template.
(enum class margin_kind): New.
(class layout_printer): Convert into a template.
(layout_printer::m_pp): Replace field with...
(layout_printer::m_sink): ...this.
(layout_printer::m_colorizer): Drop field in favor of a pointer
in the "to_text" sink.
(default_print_decoded_ch): Convert into a template.
(escape_as_bytes_print): Likewise.
(escape_as_unicode_print): Likewise.
(make_char_policy): Update to use both text and html callbacks.
(layout_printer::print_gap_in_line_numbering): Replace with...
(layout_printer<to_text>::print_gap_in_line_numbering): ...this
(layout_printer<to_html>::print_gap_in_line_numbering): ...and
this.
(layout_printer::print_source_line): Convert to template, using
m_sink.
(layout_printer::print_leftmost_column): Likewise.
(layout_printer::start_annotation_line): Likewise.
(layout_printer<to_text>::end_line): New.
(layout_printer<to_html>::end_line): New.
(layout_printer::print_annotation_line): Convert to template,
using m_sink.
(class line_label): Add field m_original_range_idx.
(layout_printer<to_text>::begin_label): New.
(layout_printer<to_html>::begin_label): New.
(layout_printer<to_text>::end_label): New.
(layout_printer<to_html>::end_label): New.
(layout_printer::print_any_labels): Convert to template, using
m_sink.
(layout_printer::print_leading_fixits): Likewise.
(layout_printer::print_trailing_fixits): Likewise.
(layout_printer::print_newline): Drop.
(layout_printer::move_to_column): Convert to template, using
m_sink.
(layout_printer::show_ruler): Likewise.
(layout_printer::print_line): Likewise.
(layout_printer::print_any_right_to_left_edge_lines): Likewise.
(layout_printer::layout_printer): Likewise.
(diagnostic_context::maybe_show_locus_as_html): New.
(diagnostic_source_print_policy::diagnostic_source_print_policy):
Update for split of start_span_cb into text vs html variants.
(diagnostic_source_print_policy::print): Update for use of
templates; use to_text.
(diagnostic_source_print_policy::print_as_html): New.
(layout_printer::print): Convert to template, using m_sink.
(selftest::make_element_for_locus): New.
(selftest::make_raw_html_for_locus): New.
(selftest::test_layout_x_offset_display_utf8): Update for use of
templates.
(selftest::test_layout_x_offset_display_tab): Likewise.
(selftest::test_one_liner_caret_and_range): Add test coverage of
HTML output.
(selftest::test_one_liner_labels): Likewise.
* diagnostic.cc (diagnostic_context::initialize): Update for split
of start_span_cb into text vs html variants.
(default_diagnostic_start_span_fn): Move to
diagnostic-show-locus.cc, converting to template.
* diagnostic.h (class xml::printer): New forward decl.
(diagnostic_start_span_fn): Replace typedef with "using",
converting to a template.
(struct to_text): New forward decl.
(struct to_html): New forward decl.
(get_printer): New decl.
(diagnostic_location_print_policy::print_text_span_start): New
decl.
(diagnostic_location_print_policy::print_html_span_start): New
decl.
(class html_label_writer): New.
(diagnostic_source_print_policy::print_as_html): New decl.
(diagnostic_source_print_policy::get_start_span_fn): Replace
with...
(diagnostic_source_print_policy::get_text_start_span_fn): ...this
(diagnostic_source_print_policy::get_html_start_span_fn): ...and
this
(diagnostic_source_print_policy::m_start_span_cb): Replace with...
(diagnostic_source_print_policy::m_text_start_span_cb): ...this
(diagnostic_source_print_policy::m_html_start_span_cb): ...and
this.
(diagnostic_context::maybe_show_locus_as_html): New decl.
(diagnostic_context::m_text_callbacks::m_start_span): Replace
with...
(diagnostic_context::m_text_callbacks::m_text_start_span): ...this
(diagnostic_context::m_text_callbacks::m_html_start_span): ...and
this.
(diagnostic_start_span): Update for template change.
(diagnostic_show_locus_as_html): New inline function.
(default_diagnostic_start_span_fn): Convert to template.
* doc/invoke.texi (experimental-html): Add "css" and "javascript"
keys.
* opts-diagnostic.cc (html_scheme_handler::make_sink): Likewise.
* selftest-diagnostic.cc
(selftest::test_diagnostic_context::start_span_cb): Update for
template changes.
* selftest-diagnostic.h
(selftest::test_diagnostic_context::start_span_cb): Likewise.
* xml-printer.h: New file.
* xml.h: New file, based on material in diagnostic-format-html.cc,
but using std::string rather than label_text.
(xml::element::m_key_insertion_order): New field.
(struct xml::raw): New.
gcc/fortran/ChangeLog
PR other/116792
* error.cc (gfc_diagnostic_start_span): Update for diagnostic.h
changes.
gcc/testsuite/ChangeLog:
PR other/116792
* gcc.dg/html-output/missing-semicolon.c: Add ":javascript=no" to
html output.
* gcc.dg/html-output/missing-semicolon.py: Move repeated
definitions into lib/htmltest.py.
* gcc.dg/plugin/diagnostic_group_plugin.cc: Update for template
changes.
* gcc.dg/plugin/diagnostic-test-metadata-html.c: Add
":javascript=no" to html output. Add
"-fdiagnostics-show-line-numbers".
* gcc.dg/plugin/diagnostic-test-metadata-html.py: Move repeated
definitions into lib/htmltest.py. Add checks of annotated source.
* gcc.dg/plugin/diagnostic-test-paths-2.c: Add ":javascript=no" to
html output.
* gcc.dg/plugin/diagnostic-test-paths-2.py: Move repeated
definitions into lib/htmltest.py. Add checks of execution path.
* gcc.dg/plugin/diagnostic-test-paths-4.c: Add
-fdiagnostics-add-output=experimental-html:javascript=no. Add
invocation ot diagnostic-test-paths-4.py.
* gcc.dg/plugin/diagnostic-test-paths-4.py: New test script.
* gcc.dg/plugin/diagnostic-test-show-locus-bw-line-numbers.c: Add
-fdiagnostics-add-output=experimental-html:javascript=no. Add
invocation of diagnostic-test-show-locus.py.
* gcc.dg/plugin/diagnostic-test-show-locus.py: New test script.
* lib/htmltest.py: New test support script.
Signed-off-by: David Malcolm <dmalcolm@redhat.com>