Update HTML output so that it renders highlight-a vs highlight-b
via <span> tags in the message itself, in the quoted source line,
in the underlines, and in the labels and their vertical bars.

Example output can be seen at:
  https://dmalcolm.fedorapeople.org/gcc/2025-05-28/diagnostic-ranges.c.html

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r16-1024-ga746747e2102c9.

gcc/ChangeLog:
        PR other/116792
        * diagnostic-format-html.cc (HTML_STYLE): Add ".highlight-a" and
        ".highlight-b".
        (html_builder::make_element_for_diagnostic): Handle begin_color
        and end_color.
        * diagnostic-show-locus.cc (to_html::to_html): Add "richloc"
        param and use it to initialize m_richloc.
        (to_html::colorize_text_for_range_idx): Drop.
        (to_html::get_location_range_by_idx): New.
        (to_html::get_highlight_color_for_range_idx): New.
        (to_html::m_richloc): New field.
        (print_html_span_start): Update for new param of to_html ctor.
        (line_printer::m_was_in_range_p): New field.
        (line_printer::m_last_range_idx): New field.
        (layout_printer<Sink>::print_source_line): Use set_in_range
        and set_outside_range rather than colorization calls.
        (layout_printer<Sink>::set_in_range): New.
        (layout_printer<Sink>::set_outside_range): New.
        (layout_printer<Sink>::print_annotation_line): Use set_in_range
        and set_outside_range rather than colorization calls.
        (layout_printer<to_text>::begin_label): Convert param from label
        to state_idx.  Add "is_label_text" param and use it to guard logic
        for turning off colorization within paths.
        (layout_printer<to_html>::begin_label): Likewise.  Push <span>
        for any highlight color.
        (layout_printer<to_text>::end_label): Likewise.
        (layout_printer<to_text>::end_label): Likewise, popping the
        <span>.
        (layout_printer<Sink>::print_any_labels): Convert begin/end_label
        calls to pass in state_idx rather than label.  Use begin/end_label
        rather than colorization calls.
        (layout_printer<Sink>::layout_printer): Likewise.
        (layout_printer<Sink>::layout_printer): Initialize new fields.
        (diagnostic_source_print_policy::print_as_html): Update for new
        param of to_html ctor.

gcc/testsuite/ChangeLog:
        PR other/116792
        * gcc.dg/format/diagnostic-ranges-html.py: New test script.
        * gcc.dg/format/diagnostic-ranges.c: Add HTML generation to
        options, and invoke the new script to check the HTML output.

Signed-off-by: David Malcolm <dmalc...@redhat.com>
---
 gcc/diagnostic-format-html.cc                 |  13 +-
 gcc/diagnostic-show-locus.cc                  | 140 ++++++++++++++----
 .../gcc.dg/format/diagnostic-ranges-html.py   |  99 +++++++++++++
 .../gcc.dg/format/diagnostic-ranges.c         |   6 +-
 4 files changed, 230 insertions(+), 28 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py

diff --git a/gcc/diagnostic-format-html.cc b/gcc/diagnostic-format-html.cc
index c430bdac75ae..05d4273c2c61 100644
--- a/gcc/diagnostic-format-html.cc
+++ b/gcc/diagnostic-format-html.cc
@@ -511,6 +511,10 @@ static const char * const HTML_STYLE
      "                    box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.5); }\n"
      "    .frame-funcname { text-align: right;\n"
      "                      font-style: italic; } \n"
+     "    .highlight-a { color: #703fec;\n" // pf-purple-400
+     "                   font-weight: bold; }\n"
+     "    .highlight-b { color: #3f9c35;\n" // pf-green-400
+     "                   font-weight: bold; }\n"
      "  </style>\n");
 
 /* A little JavaScript for ease of navigation.
@@ -725,8 +729,15 @@ html_builder::make_element_for_diagnostic (const 
diagnostic_info &diagnostic,
            break;
 
          case pp_token::kind::begin_color:
+           {
+             pp_token_begin_color *sub = as_a <pp_token_begin_color *> (iter);
+             gcc_assert (sub->m_value.get ());
+             m_xp.push_tag_with_class ("span", sub->m_value.get ());
+           }
+           break;
+
          case pp_token::kind::end_color:
-           /* These are no-ops.  */
+           m_xp.pop_tag ();
            break;
 
          case pp_token::kind::begin_quote:
diff --git a/gcc/diagnostic-show-locus.cc b/gcc/diagnostic-show-locus.cc
index c6a0cd80ce8c..3654102509b9 100644
--- a/gcc/diagnostic-show-locus.cc
+++ b/gcc/diagnostic-show-locus.cc
@@ -539,8 +539,10 @@ struct to_html
   friend class layout_printer<to_html>;
 
   to_html (xml::printer &xp,
+          const rich_location *richloc,
           html_label_writer *html_label_writer)
   : m_xp (xp),
+    m_richloc (richloc),
     m_html_label_writer (html_label_writer)
   {}
 
@@ -620,11 +622,6 @@ struct to_html
     // no-op for HTML
   }
 
-  void colorize_text_for_range_idx (int)
-  {
-    // no-op for HTML
-  }
-
   void colorize_text_for_cfg_edge ()
   {
     // no-op for HTML
@@ -648,7 +645,26 @@ struct to_html
     source_policy.get_html_start_span_fn () (loc_policy, *this, exploc);
   }
 
+  const location_range *
+  get_location_range_by_idx (int range_idx)
+  {
+    if (!m_richloc)
+      return nullptr;
+    return m_richloc->get_range (range_idx);
+  }
+
+  const char *
+  get_highlight_color_for_range_idx (int range_idx)
+  {
+    const location_range *const loc_range
+      = get_location_range_by_idx (range_idx);
+    if (!loc_range)
+      return nullptr;
+    return loc_range->m_highlight_color;
+  }
+
   xml::printer &m_xp;
+  const rich_location *m_richloc;
 private:
   html_label_writer *m_html_label_writer;
   pretty_printer m_scratch_pp;
@@ -671,7 +687,7 @@ print_html_span_start (const diagnostic_context &dc,
                       xml::printer &xp,
                       const expanded_location &exploc)
 {
-  to_html sink (xp, nullptr);
+  to_html sink (xp, nullptr, nullptr);
   diagnostic_source_print_policy source_policy (dc);
   source_policy.get_html_start_span_fn () (*this, sink, exploc);
 }
@@ -835,8 +851,8 @@ private:
   void end_line ();
   void print_annotation_line (linenum_type row, const line_bounds lbounds);
   void print_any_labels (linenum_type row);
-  void begin_label (const line_label &label);
-  void end_label ();
+  void begin_label (int state_idx, bool is_label_text);
+  void end_label (int state_idx, bool is_label_text);
   void print_trailing_fixits (linenum_type row);
 
   void
@@ -844,11 +860,17 @@ private:
 
   void print_any_right_to_left_edge_lines ();
 
+  void set_in_range (int range_idx);
+  void set_outside_range ();
+
 private:
   Sink &m_sink;
   const layout &m_layout;
   bool m_is_diagnostic_path;
 
+  bool m_was_in_range_p;
+  int m_last_range_idx;
+
   /* Fields for handling links between labels (e.g. for showing CFG edges
      in execution paths).
      Note that the logic for printing such links makes various simplifying
@@ -2302,9 +2324,9 @@ layout_printer<Sink>::print_source_line (linenum_type row,
                                                    CU_BYTES,
                                                    &state);
          if (in_range_p)
-           m_sink.colorize_text_for_range_idx (state.range_idx);
+           set_in_range (state.range_idx);
          else
-           m_sink.colorize_text_ensure_normal ();
+           set_outside_range ();
        }
 
       /* Get the display width of the next character to be output, expanding
@@ -2335,6 +2357,7 @@ layout_printer<Sink>::print_source_line (linenum_type row,
       m_sink.print_decoded_char (m_layout.m_char_policy, cp);
       c = dw.next_byte ();
     }
+  set_outside_range ();
   end_line ();
   return lbounds;
 }
@@ -2477,6 +2500,41 @@ layout_printer<to_html>::end_line ()
   m_sink.pop_html_tag ("tr");
 }
 
+/* Handle the various transitions between being-in-range and
+   not-being-in-a-range, and between ranges.  */
+
+template<typename Sink>
+void
+layout_printer<Sink>::set_in_range (int range_idx)
+{
+  if (m_was_in_range_p)
+    {
+      if (m_last_range_idx != range_idx)
+       {
+         /* transition between ranges.  */
+         end_label (m_last_range_idx, false);
+         begin_label (range_idx, false);
+       }
+    }
+  else
+    {
+      /* transition from "not in a range" to "in a range".  */
+      begin_label (range_idx, false);
+      m_was_in_range_p = true;
+    }
+  m_last_range_idx = range_idx;
+}
+
+template<typename Sink>
+void
+layout_printer<Sink>::set_outside_range ()
+{
+  if (m_was_in_range_p)
+    /* transition from "in a range" to "not in a range".  */
+    end_label (m_last_range_idx, false);
+  m_was_in_range_p = false;
+}
+
 /* Print a line consisting of the caret/underlines for the given
    source line.  */
 
@@ -2500,10 +2558,14 @@ layout_printer<Sink>::print_annotation_line 
(linenum_type row,
                                                lbounds.m_last_non_ws_disp_col,
                                                CU_DISPLAY_COLS,
                                                &state);
+      if (in_range_p)
+       set_in_range (state.range_idx);
+      else
+       set_outside_range ();
+
       if (in_range_p)
        {
          /* Within a range.  Draw either the caret or an underline.  */
-         m_sink.colorize_text_for_range_idx (state.range_idx);
          if (state.draw_caret_p)
            {
              /* Draw the caret.  */
@@ -2520,11 +2582,12 @@ layout_printer<Sink>::print_annotation_line 
(linenum_type row,
       else
        {
          /* Not in a range.  */
-         m_sink.colorize_text_ensure_normal ();
          m_sink.add_character (' ');
        }
     }
 
+  set_outside_range ();
+
   end_line ();
 }
 
@@ -2606,36 +2669,59 @@ public:
   bool m_has_out_edge;
 };
 
+/* Implementations of layout_printer::{begin,end}_label for
+   to_text and to_html.
+
+   RANGE_IDX is the index of the range within the rich_location.
+
+   IS_LABEL_TEXT is true for the text of the label,
+   false when quoting the source code, underlining the source
+   code, and for the vertical bars connecting the underlines
+   to the text of the label.  */
+
 template<>
 void
-layout_printer<to_text>::begin_label (const line_label &label)
+layout_printer<to_text>::begin_label (int range_idx,
+                                     bool is_label_text)
 {
-  /* Colorize the text, unless it's for events in a
+  /* Colorize the text, unless it's for labels for events in a
      diagnostic_path.  */
-  if (!m_is_diagnostic_path)
-    m_sink.colorize_text_for_range_idx (label.m_state_idx);
+  if (is_label_text && m_is_diagnostic_path)
+    return;
+
+  gcc_assert (m_sink.m_colorizer);
+  m_sink.m_colorizer->set_range (range_idx);
 }
 
 template<>
 void
-layout_printer<to_html>::begin_label (const line_label &)
+layout_printer<to_html>::begin_label (int range_idx,
+                                     bool is_label_text)
 {
-  if (m_sink.m_html_label_writer)
+  if (is_label_text && m_sink.m_html_label_writer)
     m_sink.m_html_label_writer->begin_label ();
+
+  if (const char *highlight_color
+       = m_sink.get_highlight_color_for_range_idx (range_idx))
+    m_sink.m_xp.push_tag_with_class ("span", highlight_color);
 }
 
 template<>
 void
-layout_printer<to_text>::end_label ()
+layout_printer<to_text>::end_label (int, bool)
 {
   m_sink.colorize_text_ensure_normal ();
 }
 
 template<>
 void
-layout_printer<to_html>::end_label ()
+layout_printer<to_html>::end_label (int range_idx,
+                                   bool is_label_text)
 {
-  if (m_sink.m_html_label_writer)
+  if (m_sink.get_highlight_color_for_range_idx (range_idx))
+    m_sink.m_xp.pop_tag ();
+
+  if (is_label_text && m_sink.m_html_label_writer)
     m_sink.m_html_label_writer->end_label ();
 }
 
@@ -2804,9 +2890,9 @@ layout_printer<Sink>::print_any_labels (linenum_type row)
                  move_to_column (&column, label->m_column, true);
                gcc_assert (column == label->m_column);
 
-               begin_label (*label);
+               begin_label (label->m_state_idx, true);
                m_sink.add_text (label->m_text.m_buffer);
-               end_label ();
+               end_label (label->m_state_idx, true);
 
                column += label->m_display_width;
                if (get_options ().show_event_links_p && label->m_has_out_edge)
@@ -2837,9 +2923,9 @@ layout_printer<Sink>::print_any_labels (linenum_type row)
              {
                gcc_assert (column <= label->m_column);
                move_to_column (&column, label->m_column, true);
-               m_sink.colorize_text_for_range_idx (label->m_state_idx);
+               begin_label (label->m_state_idx, false);
                m_sink.add_character ('|');
-               m_sink.colorize_text_ensure_normal ();
+               end_label (label->m_state_idx, false);
                column++;
              }
          }
@@ -3676,6 +3762,8 @@ layout_printer<Sink>::layout_printer (Sink &sink,
 : m_sink (sink),
   m_layout (layout),
   m_is_diagnostic_path (is_diagnostic_path),
+  m_was_in_range_p (false),
+  m_last_range_idx (0),
   m_link_lhs_state (link_lhs_state::none),
   m_link_rhs_column (-1)
 {
@@ -3857,7 +3945,7 @@ diagnostic_source_print_policy::print_as_html 
(xml::printer &xp,
   const
 {
   layout layout (*this, richloc, effects);
-  to_html sink (xp, label_writer);
+  to_html sink (xp, &richloc, label_writer);
   layout_printer<to_html> lp (sink, layout,
                              diagnostic_kind == DK_DIAGNOSTIC_PATH);
   lp.print (*this);
diff --git a/gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py 
b/gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py
new file mode 100644
index 000000000000..91383d64457a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py
@@ -0,0 +1,99 @@
+from htmltest import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def html_tree():
+    return html_tree_from_env()
+
+def assert_highlighted_text(element, expected_highlight, expected_text):
+    assert_tag(element, 'span')
+    assert_class(element, expected_highlight)
+    assert element.text == expected_text
+
+def test_message(html_tree):
+    """
+    Verify that the quoted text in the message has the correct
+    highlight colors.
+    """
+    diag = get_diag_by_index(html_tree, 0)
+    msg = get_message_within_diag(diag)
+
+    assert_tag(msg[0], 'span')
+    assert_class(msg[0], 'gcc-quoted-text')
+    assert_highlighted_text(msg[0][0], 'highlight-a', '%i')
+
+    assert_tag(msg[1], 'span')
+    assert_class(msg[1], 'gcc-quoted-text')
+    assert_highlighted_text(msg[1][0], 'highlight-a', 'int')
+
+    assert_tag(msg[2], 'span')
+    assert_class(msg[2], 'gcc-quoted-text')
+    assert_highlighted_text(msg[2][0], 'highlight-b', 'const char *')
+
+def test_annotations(html_tree):
+    """
+    Verify that the labels in the annotations have the correct
+    highlight colors.
+    """
+    diag = get_diag_by_index(html_tree, 0)
+    locus = get_locus_within_diag(diag)
+    tbody = locus.find('xhtml:tbody', ns)
+    assert tbody.attrib['class'] == 'line-span'
+
+    rows = tbody.findall('xhtml:tr', ns)
+
+    # Source row
+    row = rows[0]
+    tds = row.findall('xhtml:td', ns)
+    assert len(tds) == 2
+    assert_class(tds[1], 'source')
+    assert_highlighted_text(tds[1][0], 'highlight-a', '%i')
+    assert_highlighted_text(tds[1][1], 'highlight-b', 'msg')
+    
+    # Underline row:
+    row = rows[1]
+    tds = row.findall('xhtml:td', ns)
+    assert len(tds) == 2
+    assert_class(tds[1], 'annotation')
+    assert_highlighted_text(tds[1][0], 'highlight-a', '~^')
+    assert_highlighted_text(tds[1][1], 'highlight-b', '~~~')
+    
+    # vline row:
+    row = rows[2]
+    tds = row.findall('xhtml:td', ns)
+    assert len(tds) == 2
+    assert_class(tds[1], 'annotation')
+    assert_highlighted_text(tds[1][0], 'highlight-a', '|')
+    assert_highlighted_text(tds[1][1], 'highlight-b', '|')
+    
+    # Label row:
+    row = rows[3]
+    tds = row.findall('xhtml:td', ns)
+    assert len(tds) == 2
+    assert_class(tds[1], 'annotation')
+    assert_highlighted_text(tds[1][0], 'highlight-a', 'int')
+    assert_highlighted_text(tds[1][1], 'highlight-b', 'const char *')
+
+# For reference, here's the generated HTML:
+"""
+        <span class="gcc-message" id="gcc-diag-0-message">format &apos;<span 
class="gcc-quoted-text"><span class="high
+light-a">%i</span></span>&apos; expects argument of type &apos;<span 
class="gcc-quoted-text"><span class="highlight-a"
+>int</span></span>&apos;, but argument 2 has type &apos;<span 
class="gcc-quoted-text"><span class="highlight-b">const 
+char *</span></span>&apos;</span>
+         
+        <span class="gcc-option">[<a 
href="https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wformat";>-Wfo
+rmat=</a>]</span>
+        <table class="locus">
+          <tbody class="line-span">
+            <tr><td class="left-margin"> </td><td class="source">  
printf(&quot;hello <span class="highlight-a">%i</span>&quot;, <span 
class="highlight-b">msg</span>);  /* { dg-warning &quot;format &apos;%i&apos; 
expects argument of type &apos;int&apos;, but argument 2 has type &apos;const 
char \\*&apos; &quot; } */</td></tr>
+            <tr><td class="left-margin"> </td><td class="annotation">          
      <span class="highlight-a">~^</spa
+n>   <span class="highlight-b">~~~</span></td></tr>
+            <tr><td class="left-margin"> </td><td class="annotation">          
       <span class="highlight-a">|</spa
+n>   <span class="highlight-b">|</span></td></tr>
+            <tr><td class="left-margin"> </td><td class="annotation">          
       <span class="highlight-a">int</s
+pan> <span class="highlight-b">const char *</span></td></tr>
+            <tr><td class="left-margin"> </td><td class="annotation">          
      %s</td></tr>
+          </tbody>
+        </table>
+"""
diff --git a/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c 
b/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c
index 2c33ce2c0d56..d3e334d581c9 100644
--- a/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c
+++ b/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c
@@ -1,4 +1,4 @@
-/* { dg-options "-Wformat -fdiagnostics-show-caret" } */
+/* { dg-options "-Wformat -fdiagnostics-show-caret 
-fdiagnostics-add-output=experimental-html:javascript=no" } */
 
 /* See PR 52952. */
 
@@ -390,3 +390,7 @@ void test_const_arrays (void)
                        double
    { dg-end-multiline-output "" } */
 }
+
+/* Use a Python script to verify various properties about the generated
+   HTML file:
+   { dg-final { run-html-pytest diagnostic-ranges.c 
"diagnostic-ranges-html.py" } } */
-- 
2.26.3

Reply via email to