[Fortran] Fix error cleanup of select rank (PR93522)
[gcc.git] / gcc / diagnostic-show-locus.c
index 8bf4d9e2c85e1945b5e8fe8b66d4cccaf7e04ed3..4618b4edb7d19846e2ce74ad07fc26212d66e761 100644 (file)
@@ -1,5 +1,5 @@
 /* Diagnostic subroutines for printing source-code
-   Copyright (C) 1999-2017 Free Software Foundation, Inc.
+   Copyright (C) 1999-2020 Free Software Foundation, Inc.
    Contributed by Gabriel Dos Reis <gdr@codesourcery.com>
 
 This file is part of GCC.
@@ -27,7 +27,10 @@ along with GCC; see the file COPYING3.  If not see
 #include "backtrace.h"
 #include "diagnostic.h"
 #include "diagnostic-color.h"
+#include "gcc-rich-location.h"
 #include "selftest.h"
+#include "selftest-diagnostic.h"
+#include "cpplib.h"
 
 #ifdef HAVE_TERMIOS_H
 # include <termios.h>
@@ -37,6 +40,13 @@ along with GCC; see the file COPYING3.  If not see
 # include <sys/ioctl.h>
 #endif
 
+/* Disable warnings about quoting issues in the pp_xxx calls below
+   that (intentionally) don't follow GCC diagnostic conventions.  */
+#if __GNUC__ >= 10
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Wformat-diag"
+#endif
+
 /* Classes for rendering source code and diagnostics, within an
    anonymous namespace.
    The work is done by "class layout", which embeds and uses
@@ -77,7 +87,17 @@ class colorizer
             diagnostic_t diagnostic_kind);
   ~colorizer ();
 
-  void set_range (int range_idx) { set_state (range_idx); }
+  void set_range (int range_idx)
+  {
+    /* Normally we emphasize the primary location, then alternate between
+       two colors for the secondary locations.
+       But if we're printing a run of events in a diagnostic path, that
+       makes no sense, so print all of them with the same colorization.  */
+    if (m_diagnostic_kind == DK_DIAGNOSTIC_PATH)
+      set_state (0);
+    else
+      set_state (range_idx);
+  }
   void set_normal_text () { set_state (STATE_NORMAL_TEXT); }
   void set_fixit_insert () { set_state (STATE_FIXIT_INSERT); }
   void set_fixit_delete () { set_state (STATE_FIXIT_DELETE); }
@@ -96,7 +116,6 @@ class colorizer
   diagnostic_context *m_context;
   diagnostic_t m_diagnostic_kind;
   int m_current_state;
-  const char *m_caret;
   const char *m_range1;
   const char *m_range2;
   const char *m_fixit_insert;
@@ -104,18 +123,81 @@ class colorizer
   const char *m_stop_color;
 };
 
-/* A point within a layout_range; similar to an expanded_location,
+/* In order to handle multibyte sources properly, all of this logic needs to be
+   aware of the distinction between the number of bytes and the number of
+   display columns occupied by a character, which are not the same for non-ASCII
+   characters.  For example, the Unicode pi symbol, U+03C0, is encoded in UTF-8
+   as "\xcf\x80", and thus occupies 2 bytes of space while only occupying 1
+   display column when it is output.  A typical emoji, such as U+1F602 (in
+   UTF-8, "\xf0\x9f\x98\x82"), requires 4 bytes and has a display width of 2.
+
+   The below example line, which is also used for selftests below, shows how the
+   display column and byte column are related:
+
+     0000000001111111111222222   display
+     1234567890123456789012345   columns
+     SS_foo = P_bar.SS_fieldP;
+     0000000111111111222222223   byte
+     1356789012456789134567891   columns
+
+   Here SS represents the two display columns for the U+1F602 emoji, and P
+   represents the one display column for the U+03C0 pi symbol.  As an example, a
+   diagnostic pointing to the final P on this line is at byte column 29 and
+   display column 24.  This reflects the fact that the three extended characters
+   before the final P occupy cumulatively 5 more bytes than they do display
+   columns (a difference of 2 for each of the two SSs, and one for the other P).
+
+   One or the other of the two column units is more useful depending on the
+   context.  For instance, in order to output the caret at the correct location,
+   we need to count display columns; in order to colorize a source line, we need
+   to count the bytes.  All locations are provided to us as byte counts, which
+   we augment with the display column on demand so that it can be used when
+   needed.  This is not the most efficient way to do things since it requires
+   looping over the whole line each time, but it should be fine for the purpose
+   of outputting diagnostics.
+
+   In order to keep straight which units (byte or display) are in use at a
+   given time, the following enum lets us specify that explicitly.  */
+
+enum column_unit {
+  /* Measured in raw bytes.  */
+  CU_BYTES = 0,
+
+  /* Measured in display units.  */
+  CU_DISPLAY_COLS,
+
+  /* For arrays indexed by column_unit.  */
+  CU_NUM_UNITS
+};
+
+/* Utility class to augment an exploc with the corresponding display column.  */
+
+class exploc_with_display_col : public expanded_location
+{
+ public:
+  exploc_with_display_col (const expanded_location &exploc)
+    : expanded_location (exploc),
+      m_display_col (location_compute_display_column (exploc)) {}
+
+  int m_display_col;
+};
+
+
+/* A point within a layout_range; similar to an exploc_with_display_col,
    but after filtering on file.  */
 
 class layout_point
 {
  public:
   layout_point (const expanded_location &exploc)
-  : m_line (exploc.line),
-    m_column (exploc.column) {}
+    : m_line (exploc.line)
+  {
+    m_columns[CU_BYTES] = exploc.column;
+    m_columns[CU_DISPLAY_COLS] = location_compute_display_column (exploc);
+  }
 
-  int m_line;
-  int m_column;
+  linenum_type m_line;
+  int m_columns[CU_NUM_UNITS];
 };
 
 /* A class for use by "class layout" below: a filtered location_range.  */
@@ -125,16 +207,21 @@ class layout_range
  public:
   layout_range (const expanded_location *start_exploc,
                const expanded_location *finish_exploc,
-               bool show_caret_p,
-               const expanded_location *caret_exploc);
+               enum range_display_kind range_display_kind,
+               const expanded_location *caret_exploc,
+               unsigned original_idx,
+               const range_label *label);
 
-  bool contains_point (int row, int column) const;
-  bool intersects_line_p (int row) const;
+  bool contains_point (linenum_type row, int column,
+                      enum column_unit col_unit) const;
+  bool intersects_line_p (linenum_type row) const;
 
   layout_point m_start;
   layout_point m_finish;
-  bool m_show_caret_p;
+  enum range_display_kind m_range_display_kind;
   layout_point m_caret;
+  unsigned m_original_idx;
+  const range_label *m_label;
 };
 
 /* A struct for use by layout::print_source_line for telling
@@ -145,6 +232,17 @@ struct line_bounds
 {
   int m_first_non_ws;
   int m_last_non_ws;
+
+  void convert_to_display_cols (char_span line)
+  {
+    m_first_non_ws = cpp_byte_column_to_display_column (line.get_buffer (),
+                                                       line.length (),
+                                                       m_first_non_ws);
+
+    m_last_non_ws = cpp_byte_column_to_display_column (line.get_buffer (),
+                                                      line.length (),
+                                                      m_last_non_ws);
+  }
 };
 
 /* A range of contiguous source lines within a layout (e.g. "lines 5-10"
@@ -152,8 +250,9 @@ struct line_bounds
    splits the pertinent source lines into a list of disjoint line_span
    instances (e.g. lines 5-10, lines 15-20, line 23).  */
 
-struct line_span
+class line_span
 {
+public:
   line_span (linenum_type first_line, linenum_type last_line)
     : m_first_line (first_line), m_last_line (last_line)
   {
@@ -171,16 +270,52 @@ struct line_span
   {
     const line_span *ls1 = (const line_span *)p1;
     const line_span *ls2 = (const line_span *)p2;
-    int first_line_diff = (int)ls1->m_first_line - (int)ls2->m_first_line;
-    if (first_line_diff)
-      return first_line_diff;
-    return (int)ls1->m_last_line - (int)ls2->m_last_line;
+    int first_line_cmp = compare (ls1->m_first_line, ls2->m_first_line);
+    if (first_line_cmp)
+      return first_line_cmp;
+    return compare (ls1->m_last_line, ls2->m_last_line);
   }
 
   linenum_type m_first_line;
   linenum_type m_last_line;
 };
 
+#if CHECKING_P
+
+/* Selftests for line_span.  */
+
+static void
+test_line_span ()
+{
+  line_span line_one (1, 1);
+  ASSERT_EQ (1, line_one.get_first_line ());
+  ASSERT_EQ (1, line_one.get_last_line ());
+  ASSERT_FALSE (line_one.contains_line_p (0));
+  ASSERT_TRUE (line_one.contains_line_p (1));
+  ASSERT_FALSE (line_one.contains_line_p (2));
+
+  line_span lines_1_to_3 (1, 3);
+  ASSERT_EQ (1, lines_1_to_3.get_first_line ());
+  ASSERT_EQ (3, lines_1_to_3.get_last_line ());
+  ASSERT_TRUE (lines_1_to_3.contains_line_p (1));
+  ASSERT_TRUE (lines_1_to_3.contains_line_p (3));
+
+  ASSERT_EQ (0, line_span::comparator (&line_one, &line_one));
+  ASSERT_GT (line_span::comparator (&lines_1_to_3, &line_one), 0);
+  ASSERT_LT (line_span::comparator (&line_one, &lines_1_to_3), 0);
+
+  /* A linenum > 2^31.  */
+  const linenum_type LARGEST_LINE = 0xffffffff;
+  line_span largest_line (LARGEST_LINE, LARGEST_LINE);
+  ASSERT_EQ (LARGEST_LINE, largest_line.get_first_line ());
+  ASSERT_EQ (LARGEST_LINE, largest_line.get_last_line ());
+
+  ASSERT_GT (line_span::comparator (&largest_line, &line_one), 0);
+  ASSERT_LT (line_span::comparator (&line_one, &largest_line), 0);
+}
+
+#endif /* #if CHECKING_P */
+
 /* A class to control the overall layout when printing a diagnostic.
 
    The layout is determined within the constructor.
@@ -196,58 +331,76 @@ class layout
          rich_location *richloc,
          diagnostic_t diagnostic_kind);
 
+  bool maybe_add_location_range (const location_range *loc_range,
+                                unsigned original_idx,
+                                bool restrict_to_current_line_spans);
+
   int get_num_line_spans () const { return m_line_spans.length (); }
   const line_span *get_line_span (int idx) const { return &m_line_spans[idx]; }
 
+  int get_linenum_width () const { return m_linenum_width; }
+  int get_x_offset_display () const { return m_x_offset_display; }
+
+  void print_gap_in_line_numbering ();
   bool print_heading_for_line_span_index_p (int line_span_idx) const;
 
   expanded_location get_expanded_location (const line_span *) const;
 
-  void print_line (int row);
+  void print_line (linenum_type row);
 
  private:
-  void print_leading_fixits (int row);
-  void print_source_line (int row, const char *line, int line_width,
+  bool will_show_line_p (linenum_type row) const;
+  void print_leading_fixits (linenum_type row);
+  void print_source_line (linenum_type row, const char *line, int line_bytes,
                          line_bounds *lbounds_out);
-  bool should_print_annotation_line_p (int row) const;
-  void print_annotation_line (int row, const line_bounds lbounds);
-  void print_trailing_fixits (int row);
+  bool should_print_annotation_line_p (linenum_type row) const;
+  void start_annotation_line (char margin_char = ' ') const;
+  void print_annotation_line (linenum_type row, const line_bounds lbounds);
+  void print_any_labels (linenum_type row);
+  void print_trailing_fixits (linenum_type row);
 
-  bool annotation_line_showed_range_p (int line, int start_column,
+  bool annotation_line_showed_range_p (linenum_type line, int start_column,
                                       int finish_column) const;
   void show_ruler (int max_column) const;
 
   bool validate_fixit_hint_p (const fixit_hint *hint);
 
   void calculate_line_spans ();
+  void calculate_linenum_width ();
+  void calculate_x_offset_display ();
 
   void print_newline ();
 
   bool
   get_state_at_point (/* Inputs.  */
-                     int row, int column,
+                     linenum_type row, int column,
                      int first_non_ws, int last_non_ws,
+                     enum column_unit col_unit,
                      /* Outputs.  */
                      point_state *out_state);
 
   int
-  get_x_bound_for_row (int row, int caret_column,
+  get_x_bound_for_row (linenum_type row, int caret_column,
                       int last_non_ws);
 
   void
-  move_to_column (int *column, int dest_column);
+  move_to_column (int *column, int dest_column, bool add_left_margin);
 
  private:
   diagnostic_context *m_context;
   pretty_printer *m_pp;
-  diagnostic_t m_diagnostic_kind;
-  expanded_location m_exploc;
+  location_t m_primary_loc;
+  exploc_with_display_col m_exploc;
   colorizer m_colorizer;
   bool m_colorize_source_p;
+  bool m_show_labels_p;
+  bool m_show_line_numbers_p;
+  bool m_diagnostic_path_p;
   auto_vec <layout_range> m_layout_ranges;
   auto_vec <const fixit_hint *> m_fixit_hints;
   auto_vec <line_span> m_line_spans;
-  int m_x_offset;
+  int m_linenum_width;
+  int m_x_offset_display;
 };
 
 /* Implementation of "class colorizer".  */
@@ -362,12 +515,16 @@ colorizer::get_color_by_name (const char *name)
 
 layout_range::layout_range (const expanded_location *start_exploc,
                            const expanded_location *finish_exploc,
-                           bool show_caret_p,
-                           const expanded_location *caret_exploc)
+                           enum range_display_kind range_display_kind,
+                           const expanded_location *caret_exploc,
+                           unsigned original_idx,
+                           const range_label *label)
 : m_start (*start_exploc),
   m_finish (*finish_exploc),
-  m_show_caret_p (show_caret_p),
-  m_caret (*caret_exploc)
+  m_range_display_kind (range_display_kind),
+  m_caret (*caret_exploc),
+  m_original_idx (original_idx),
+  m_label (label)
 {
 }
 
@@ -408,10 +565,15 @@ layout_range::layout_range (const expanded_location *start_exploc,
    - 'w' indicates a point within the range
    - 'F' indicates the finish of the range (which is
         within it).
-   - 'a' indicates a subsequent point *after* the range.  */
+   - 'a' indicates a subsequent point *after* the range.
+
+   COL_UNIT controls whether we check the byte column or
+   the display column; one or the other is more convenient
+   depending on the context.  */
 
 bool
-layout_range::contains_point (int row, int column) const
+layout_range::contains_point (linenum_type row, int column,
+                             enum column_unit col_unit) const
 {
   gcc_assert (m_start.m_line <= m_finish.m_line);
   /* ...but the equivalent isn't true for the columns;
@@ -427,7 +589,7 @@ layout_range::contains_point (int row, int column) const
     /* On same line as start of range (corresponding
        to line 02 in example A and line 03 in example B).  */
     {
-      if (column < m_start.m_column)
+      if (column < m_start.m_columns[col_unit])
        /* Points on the starting line of the range, but
           before the column in which it begins.  */
        return false;
@@ -441,7 +603,7 @@ layout_range::contains_point (int row, int column) const
        {
          /* This is a single-line range.  */
          gcc_assert (row == m_finish.m_line);
-         return column <= m_finish.m_column;
+         return column <= m_finish.m_columns[col_unit];
        }
     }
 
@@ -466,13 +628,13 @@ layout_range::contains_point (int row, int column) const
 
   gcc_assert (row ==  m_finish.m_line);
 
-  return column <= m_finish.m_column;
+  return column <= m_finish.m_columns[col_unit];
 }
 
 /* Does this layout_range contain any part of line ROW?  */
 
 bool
-layout_range::intersects_line_p (int row) const
+layout_range::intersects_line_p (linenum_type row) const
 {
   gcc_assert (m_start.m_line <= m_finish.m_line);
   if (row < m_start.m_line)
@@ -484,17 +646,24 @@ layout_range::intersects_line_p (int row) const
 
 #if CHECKING_P
 
-/* A helper function for testing layout_range.  */
+/* Create some expanded locations for testing layout_range.  The filename
+   member of the explocs is set to the empty string.  This member will only be
+   inspected by the calls to location_compute_display_column() made from the
+   layout_point constructors.  That function will check for an empty filename
+   argument and not attempt to open it, rather treating the non-existent data
+   as if the display width were the same as the byte count.  Tests exercising a
+   real difference between byte count and display width are performed later,
+   e.g. in test_diagnostic_show_locus_one_liner_utf8().  */
 
 static layout_range
 make_range (int start_line, int start_col, int end_line, int end_col)
 {
   const expanded_location start_exploc
-    = {"test.c", start_line, start_col, NULL, false};
+    = {"", start_line, start_col, NULL, false};
   const expanded_location finish_exploc
-    = {"test.c", end_line, end_col, NULL, false};
-  return layout_range (&start_exploc, &finish_exploc, false,
-                      &start_exploc);
+    = {"", end_line, end_col, NULL, false};
+  return layout_range (&start_exploc, &finish_exploc, SHOW_RANGE_WITHOUT_CARET,
+                      &start_exploc, 0, NULL);
 }
 
 /* Selftests for layout_range::contains_point and
@@ -510,20 +679,25 @@ test_layout_range_for_single_point ()
 
   /* Tests for layout_range::contains_point.  */
 
-  /* Before the line. */
-  ASSERT_FALSE (point.contains_point (6, 1));
+  for (int i = 0; i != CU_NUM_UNITS; ++i)
+    {
+      const enum column_unit col_unit = (enum column_unit) i;
+
+      /* Before the line.  */
+      ASSERT_FALSE (point.contains_point (6, 1, col_unit));
 
-  /* On the line, but before start.  */
-  ASSERT_FALSE (point.contains_point (7, 9));
+      /* On the line, but before start.  */
+      ASSERT_FALSE (point.contains_point (7, 9, col_unit));
 
-  /* At the point.  */
-  ASSERT_TRUE (point.contains_point (7, 10));
+      /* At the point.  */
+      ASSERT_TRUE (point.contains_point (7, 10, col_unit));
 
-  /* On the line, after the point.  */
-  ASSERT_FALSE (point.contains_point (7, 11));
+      /* On the line, after the point.  */
+      ASSERT_FALSE (point.contains_point (7, 11, col_unit));
 
-  /* After the line.  */
-  ASSERT_FALSE (point.contains_point (8, 1));
+      /* After the line.  */
+      ASSERT_FALSE (point.contains_point (8, 1, col_unit));
+    }
 
   /* Tests for layout_range::intersects_line_p.  */
   ASSERT_FALSE (point.intersects_line_p (6));
@@ -541,26 +715,31 @@ test_layout_range_for_single_line ()
 
   /* Tests for layout_range::contains_point.  */
 
-  /* Before the line. */
-  ASSERT_FALSE (example_a.contains_point (1, 1));
+  for (int i = 0; i != CU_NUM_UNITS; ++i)
+    {
+      const enum column_unit col_unit = (enum column_unit) i;
+
+      /* Before the line.  */
+      ASSERT_FALSE (example_a.contains_point (1, 1, col_unit));
 
-  /* On the line, but before start.  */
-  ASSERT_FALSE (example_a.contains_point (2, 21));
+      /* On the line, but before start.  */
+      ASSERT_FALSE (example_a.contains_point (2, 21, col_unit));
 
-  /* On the line, at the start.  */
-  ASSERT_TRUE (example_a.contains_point (2, 22));
+      /* On the line, at the start.  */
+      ASSERT_TRUE (example_a.contains_point (2, 22, col_unit));
 
-  /* On the line, within the range.  */
-  ASSERT_TRUE (example_a.contains_point (2, 23));
+      /* On the line, within the range.  */
+      ASSERT_TRUE (example_a.contains_point (2, 23, col_unit));
 
-  /* On the line, at the end.  */
-  ASSERT_TRUE (example_a.contains_point (2, 38));
+      /* On the line, at the end.  */
+      ASSERT_TRUE (example_a.contains_point (2, 38, col_unit));
 
-  /* On the line, after the end.  */
-  ASSERT_FALSE (example_a.contains_point (2, 39));
+      /* On the line, after the end.  */
+      ASSERT_FALSE (example_a.contains_point (2, 39, col_unit));
 
-  /* After the line.  */
-  ASSERT_FALSE (example_a.contains_point (2, 39));
+      /* After the line.  */
+      ASSERT_FALSE (example_a.contains_point (2, 39, col_unit));
+    }
 
   /* Tests for layout_range::intersects_line_p.  */
   ASSERT_FALSE (example_a.intersects_line_p (1));
@@ -578,40 +757,45 @@ test_layout_range_for_multiple_lines ()
 
   /* Tests for layout_range::contains_point.  */
 
-  /* Before first line. */
-  ASSERT_FALSE (example_b.contains_point (1, 1));
+  for (int i = 0; i != CU_NUM_UNITS; ++i)
+    {
+      const enum column_unit col_unit = (enum column_unit) i;
+
+      /* Before first line.  */
+      ASSERT_FALSE (example_b.contains_point (1, 1, col_unit));
 
-  /* On the first line, but before start.  */
-  ASSERT_FALSE (example_b.contains_point (3, 13));
+      /* On the first line, but before start.  */
+      ASSERT_FALSE (example_b.contains_point (3, 13, col_unit));
 
-  /* At the start.  */
-  ASSERT_TRUE (example_b.contains_point (3, 14));
+      /* At the start.  */
+      ASSERT_TRUE (example_b.contains_point (3, 14, col_unit));
 
-  /* On the first line, within the range.  */
-  ASSERT_TRUE (example_b.contains_point (3, 15));
+      /* On the first line, within the range.  */
+      ASSERT_TRUE (example_b.contains_point (3, 15, col_unit));
 
-  /* On an interior line.
-     The column number should not matter; try various boundary
-     values.  */
-  ASSERT_TRUE (example_b.contains_point (4, 1));
-  ASSERT_TRUE (example_b.contains_point (4, 7));
-  ASSERT_TRUE (example_b.contains_point (4, 8));
-  ASSERT_TRUE (example_b.contains_point (4, 9));
-  ASSERT_TRUE (example_b.contains_point (4, 13));
-  ASSERT_TRUE (example_b.contains_point (4, 14));
-  ASSERT_TRUE (example_b.contains_point (4, 15));
+      /* On an interior line.
+        The column number should not matter; try various boundary
+        values.  */
+      ASSERT_TRUE (example_b.contains_point (4, 1, col_unit));
+      ASSERT_TRUE (example_b.contains_point (4, 7, col_unit));
+      ASSERT_TRUE (example_b.contains_point (4, 8, col_unit));
+      ASSERT_TRUE (example_b.contains_point (4, 9, col_unit));
+      ASSERT_TRUE (example_b.contains_point (4, 13, col_unit));
+      ASSERT_TRUE (example_b.contains_point (4, 14, col_unit));
+      ASSERT_TRUE (example_b.contains_point (4, 15, col_unit));
 
-  /* On the final line, before the end.  */
-  ASSERT_TRUE (example_b.contains_point (5, 7));
+      /* On the final line, before the end.  */
+      ASSERT_TRUE (example_b.contains_point (5, 7, col_unit));
 
-  /* On the final line, at the end.  */
-  ASSERT_TRUE (example_b.contains_point (5, 8));
+      /* On the final line, at the end.  */
+      ASSERT_TRUE (example_b.contains_point (5, 8, col_unit));
 
-  /* On the final line, after the end.  */
-  ASSERT_FALSE (example_b.contains_point (5, 9));
+      /* On the final line, after the end.  */
+      ASSERT_FALSE (example_b.contains_point (5, 9, col_unit));
 
-  /* After the line.  */
-  ASSERT_FALSE (example_b.contains_point (6, 1));
+      /* After the line.  */
+      ASSERT_FALSE (example_b.contains_point (6, 1, col_unit));
+    }
 
   /* Tests for layout_range::intersects_line_p.  */
   ASSERT_FALSE (example_b.intersects_line_p (2));
@@ -623,53 +807,56 @@ test_layout_range_for_multiple_lines ()
 
 #endif /* #if CHECKING_P */
 
-/* Given a source line LINE of length LINE_WIDTH, determine the width
-   without any trailing whitespace.  */
+/* Given a source line LINE of length LINE_BYTES bytes, determine the length
+   (still in bytes, not display cols) without any trailing whitespace.  */
 
 static int
-get_line_width_without_trailing_whitespace (const char *line, int line_width)
+get_line_bytes_without_trailing_whitespace (const char *line, int line_bytes)
 {
-  int result = line_width;
+  int result = line_bytes;
   while (result > 0)
     {
       char ch = line[result - 1];
-      if (ch == ' ' || ch == '\t')
+      if (ch == ' ' || ch == '\t' || ch == '\r')
        result--;
       else
        break;
     }
   gcc_assert (result >= 0);
-  gcc_assert (result <= line_width);
+  gcc_assert (result <= line_bytes);
   gcc_assert (result == 0 ||
              (line[result - 1] != ' '
-              && line[result -1] != '\t'));
+              && line[result -1] != '\t'
+              && line[result -1] != '\r'));
   return result;
 }
 
 #if CHECKING_P
 
-/* A helper function for testing get_line_width_without_trailing_whitespace.  */
+/* A helper function for testing get_line_bytes_without_trailing_whitespace.  */
 
 static void
-assert_eq (const char *line, int expected_width)
+assert_eq (const char *line, int expected_bytes)
 {
   int actual_value
-    = get_line_width_without_trailing_whitespace (line, strlen (line));
-  ASSERT_EQ (actual_value, expected_width);
+    = get_line_bytes_without_trailing_whitespace (line, strlen (line));
+  ASSERT_EQ (actual_value, expected_bytes);
 }
 
-/* Verify that get_line_width_without_trailing_whitespace is sane for
+/* Verify that get_line_bytes_without_trailing_whitespace is sane for
    various inputs.  It is not required to handle newlines.  */
 
 static void
-test_get_line_width_without_trailing_whitespace ()
+test_get_line_bytes_without_trailing_whitespace ()
 {
   assert_eq ("", 0);
   assert_eq (" ", 0);
   assert_eq ("\t", 0);
+  assert_eq ("\r", 0);
   assert_eq ("hello world", 11);
   assert_eq ("hello world     ", 11);
   assert_eq ("hello world     \t\t  ", 11);
+  assert_eq ("hello world\r", 11);
 }
 
 #endif /* #if CHECKING_P */
@@ -718,11 +905,11 @@ compatible_locations_p (location_t loc_a, location_t loc_b)
          /* Expand each location towards the spelling location, and
             recurse.  */
          const line_map_macro *macro_map = linemap_check_macro (map_a);
-         source_location loc_a_toward_spelling
+         location_t loc_a_toward_spelling
            = linemap_macro_map_loc_unwind_toward_spelling (line_table,
                                                            macro_map,
                                                            loc_a);
-         source_location loc_b_toward_spelling
+         location_t loc_b_toward_spelling
            = linemap_macro_map_loc_unwind_toward_spelling (line_table,
                                                            macro_map,
                                                            loc_b);
@@ -750,6 +937,16 @@ compatible_locations_p (location_t loc_a, location_t loc_b)
     }
 }
 
+/* Comparator for sorting fix-it hints.  */
+
+static int
+fixit_cmp (const void *p_a, const void *p_b)
+{
+  const fixit_hint * hint_a = *static_cast<const fixit_hint * const *> (p_a);
+  const fixit_hint * hint_b = *static_cast<const fixit_hint * const *> (p_b);
+  return hint_a->get_start_loc () - hint_b->get_start_loc ();
+}
+
 /* Implementation of class layout.  */
 
 /* Constructor for class layout.
@@ -758,7 +955,7 @@ compatible_locations_p (location_t loc_a, location_t loc_b)
    sanely print, populating m_layout_ranges and m_fixit_hints.
    Determine the range of lines that we will print, splitting them
    up into an ordered list of disjoint spans of contiguous line numbers.
-   Determine m_x_offset, to ensure that the primary caret
+   Determine m_x_offset_display, to ensure that the primary caret
    will fit within the max_width provided by the diagnostic_context.  */
 
 layout::layout (diagnostic_context * context,
@@ -766,83 +963,25 @@ layout::layout (diagnostic_context * context,
                diagnostic_t diagnostic_kind)
 : m_context (context),
   m_pp (context->printer),
-  m_diagnostic_kind (diagnostic_kind),
+  m_primary_loc (richloc->get_range (0)->m_loc),
   m_exploc (richloc->get_expanded_location (0)),
   m_colorizer (context, diagnostic_kind),
   m_colorize_source_p (context->colorize_source_p),
+  m_show_labels_p (context->show_labels_p),
+  m_show_line_numbers_p (context->show_line_numbers_p),
+  m_diagnostic_path_p (diagnostic_kind == DK_DIAGNOSTIC_PATH),
   m_layout_ranges (richloc->get_num_locations ()),
   m_fixit_hints (richloc->get_num_fixit_hints ()),
   m_line_spans (1 + richloc->get_num_locations ()),
-  m_x_offset (0)
+  m_linenum_width (0),
+  m_x_offset_display (0)
 {
-  source_location primary_loc = richloc->get_range (0)->m_loc;
-
   for (unsigned int idx = 0; idx < richloc->get_num_locations (); idx++)
     {
       /* This diagnostic printer can only cope with "sufficiently sane" ranges.
         Ignore any ranges that are awkward to handle.  */
       const location_range *loc_range = richloc->get_range (idx);
-
-      /* Split the "range" into caret and range information.  */
-      source_range src_range = get_range_from_loc (line_table, loc_range->m_loc);
-
-      /* Expand the various locations.  */
-      expanded_location start
-       = linemap_client_expand_location_to_spelling_point (src_range.m_start);
-      expanded_location finish
-       = linemap_client_expand_location_to_spelling_point (src_range.m_finish);
-      expanded_location caret
-       = linemap_client_expand_location_to_spelling_point (loc_range->m_loc);
-
-      /* If any part of the range isn't in the same file as the primary
-        location of this diagnostic, ignore the range.  */
-      if (start.file != m_exploc.file)
-       continue;
-      if (finish.file != m_exploc.file)
-       continue;
-      if (loc_range->m_show_caret_p)
-       if (caret.file != m_exploc.file)
-         continue;
-
-      /* Sanitize the caret location for non-primary ranges.  */
-      if (m_layout_ranges.length () > 0)
-       if (loc_range->m_show_caret_p)
-         if (!compatible_locations_p (loc_range->m_loc, primary_loc))
-           /* Discard any non-primary ranges that can't be printed
-              sanely relative to the primary location.  */
-           continue;
-
-      /* Everything is now known to be in the correct source file,
-        but it may require further sanitization.  */
-      layout_range ri (&start, &finish, loc_range->m_show_caret_p, &caret);
-
-      /* If we have a range that finishes before it starts (perhaps
-        from something built via macro expansion), printing the
-        range is likely to be nonsensical.  Also, attempting to do so
-        breaks assumptions within the printing code  (PR c/68473).
-        Similarly, don't attempt to print ranges if one or both ends
-        of the range aren't sane to print relative to the
-        primary location (PR c++/70105).  */
-      if (start.line > finish.line
-         || !compatible_locations_p (src_range.m_start, primary_loc)
-         || !compatible_locations_p (src_range.m_finish, primary_loc))
-       {
-         /* Is this the primary location?  */
-         if (m_layout_ranges.length () == 0)
-           {
-             /* We want to print the caret for the primary location, but
-                we must sanitize away m_start and m_finish.  */
-             ri.m_start = ri.m_caret;
-             ri.m_finish = ri.m_caret;
-           }
-         else
-           /* This is a non-primary range; ignore it.  */
-           continue;
-       }
-
-      /* Passed all the tests; add the range to m_layout_ranges so that
-        it will be printed.  */
-      m_layout_ranges.safe_push (ri);
+      maybe_add_location_range (loc_range, idx, false);
     }
 
   /* Populate m_fixit_hints, filtering to only those that are in the
@@ -854,29 +993,150 @@ layout::layout (diagnostic_context * context,
        m_fixit_hints.safe_push (hint);
     }
 
-  /* Populate m_line_spans.  */
+  /* Sort m_fixit_hints.  */
+  m_fixit_hints.qsort (fixit_cmp);
+
+  /* Populate the indicated members.  */
   calculate_line_spans ();
+  calculate_linenum_width ();
+  calculate_x_offset_display ();
 
-  /* Adjust m_x_offset.
-     Center the primary caret to fit in max_width; all columns
-     will be adjusted accordingly.  */
-  int max_width = m_context->caret_max_width;
-  int line_width;
-  const char *line = location_get_source_line (m_exploc.file, m_exploc.line,
-                                              &line_width);
-  if (line && m_exploc.column <= line_width)
+  if (context->show_ruler_p)
+    show_ruler (m_x_offset_display + m_context->caret_max_width);
+}
+
+
+/* Attempt to add LOC_RANGE to m_layout_ranges, filtering them to
+   those that we can sanely print.
+
+   ORIGINAL_IDX is the index of LOC_RANGE within its rich_location,
+   (for use as extrinsic state by label ranges FIXME).
+
+   If RESTRICT_TO_CURRENT_LINE_SPANS is true, then LOC_RANGE is also
+   filtered against this layout instance's current line spans: it
+   will only be added if the location is fully within the lines
+   already specified by other locations.
+
+   Return true iff LOC_RANGE was added.  */
+
+bool
+layout::maybe_add_location_range (const location_range *loc_range,
+                                 unsigned original_idx,
+                                 bool restrict_to_current_line_spans)
+{
+  gcc_assert (loc_range);
+
+  /* Split the "range" into caret and range information.  */
+  source_range src_range = get_range_from_loc (line_table, loc_range->m_loc);
+
+  /* Expand the various locations.  */
+  expanded_location start
+    = linemap_client_expand_location_to_spelling_point
+    (src_range.m_start, LOCATION_ASPECT_START);
+  expanded_location finish
+    = linemap_client_expand_location_to_spelling_point
+    (src_range.m_finish, LOCATION_ASPECT_FINISH);
+  expanded_location caret
+    = linemap_client_expand_location_to_spelling_point
+    (loc_range->m_loc, LOCATION_ASPECT_CARET);
+
+  /* If any part of the range isn't in the same file as the primary
+     location of this diagnostic, ignore the range.  */
+  if (start.file != m_exploc.file)
+    return false;
+  if (finish.file != m_exploc.file)
+    return false;
+  if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET)
+    if (caret.file != m_exploc.file)
+      return false;
+
+  /* Sanitize the caret location for non-primary ranges.  */
+  if (m_layout_ranges.length () > 0)
+    if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET)
+      if (!compatible_locations_p (loc_range->m_loc, m_primary_loc))
+       /* Discard any non-primary ranges that can't be printed
+          sanely relative to the primary location.  */
+       return false;
+
+  /* Everything is now known to be in the correct source file,
+     but it may require further sanitization.  */
+  layout_range ri (&start, &finish, loc_range->m_range_display_kind, &caret,
+                  original_idx, loc_range->m_label);
+
+  /* If we have a range that finishes before it starts (perhaps
+     from something built via macro expansion), printing the
+     range is likely to be nonsensical.  Also, attempting to do so
+     breaks assumptions within the printing code  (PR c/68473).
+     Similarly, don't attempt to print ranges if one or both ends
+     of the range aren't sane to print relative to the
+     primary location (PR c++/70105).  */
+  if (start.line > finish.line
+      || !compatible_locations_p (src_range.m_start, m_primary_loc)
+      || !compatible_locations_p (src_range.m_finish, m_primary_loc))
+    {
+      /* Is this the primary location?  */
+      if (m_layout_ranges.length () == 0)
+       {
+         /* We want to print the caret for the primary location, but
+            we must sanitize away m_start and m_finish.  */
+         ri.m_start = ri.m_caret;
+         ri.m_finish = ri.m_caret;
+       }
+      else
+       /* This is a non-primary range; ignore it.  */
+       return false;
+    }
+
+  /* Potentially filter to just the lines already specified by other
+     locations.  This is for use by gcc_rich_location::add_location_if_nearby.
+     The layout ctor doesn't use it, and can't because m_line_spans
+     hasn't been set up at that point.  */
+  if (restrict_to_current_line_spans)
     {
-      int right_margin = CARET_LINE_MARGIN;
-      int column = m_exploc.column;
-      right_margin = MIN (line_width - column, right_margin);
-      right_margin = max_width - right_margin;
-      if (line_width >= max_width && column > right_margin)
-       m_x_offset = column - right_margin;
-      gcc_assert (m_x_offset >= 0);
+      if (!will_show_line_p (start.line))
+       return false;
+      if (!will_show_line_p (finish.line))
+       return false;
+      if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET)
+       if (!will_show_line_p (caret.line))
+         return false;
     }
 
-  if (context->show_ruler_p)
-    show_ruler (m_x_offset + max_width);
+  /* Passed all the tests; add the range to m_layout_ranges so that
+     it will be printed.  */
+  m_layout_ranges.safe_push (ri);
+  return true;
+}
+
+/* Return true iff ROW is within one of the line spans for this layout.  */
+
+bool
+layout::will_show_line_p (linenum_type row) const
+{
+  for (int line_span_idx = 0; line_span_idx < get_num_line_spans ();
+       line_span_idx++)
+    {
+      const line_span *line_span = get_line_span (line_span_idx);
+      if (line_span->contains_line_p (row))
+       return true;
+    }
+  return false;
+}
+
+/* Print a line showing a gap in the line numbers, for showing the boundary
+   between two line spans.  */
+
+void
+layout::print_gap_in_line_numbering ()
+{
+  gcc_assert (m_show_line_numbers_p);
+
+  pp_emit_prefix (m_pp);
+
+  for (int i = 0; i < m_linenum_width + 1; i++)
+    pp_character (m_pp, '.');
+
+  pp_newline (m_pp);
 }
 
 /* Return true iff we should print a heading when starting the
@@ -918,7 +1178,7 @@ layout::get_expanded_location (const line_span *line_span) const
        {
          expanded_location exploc = m_exploc;
          exploc.line = lr->m_start.m_line;
-         exploc.column = lr->m_start.m_column;
+         exploc.column = lr->m_start.m_columns[CU_BYTES];
          return exploc;
        }
     }
@@ -961,7 +1221,16 @@ static line_span
 get_line_span_for_fixit_hint (const fixit_hint *hint)
 {
   gcc_assert (hint);
-  return line_span (LOCATION_LINE (hint->get_start_loc ()),
+
+  int start_line = LOCATION_LINE (hint->get_start_loc ());
+
+  /* For line-insertion fix-it hints, add the previous line to the
+     span, to give the user more context on the proposed change.  */
+  if (hint->ends_with_newline_p ())
+    if (start_line > 1)
+      start_line--;
+
+  return line_span (start_line,
                    LOCATION_LINE (hint->get_next_loc ()));
 }
 
@@ -976,21 +1245,34 @@ get_line_span_for_fixit_hint (const fixit_hint *hint)
    This function populates m_line_spans with an ordered, disjoint list of
    the line spans of interest.
 
-   For example, if the primary caret location is on line 7, with ranges
-   covering lines 5-6 and lines 9-12:
+   Printing a gap between line spans takes one line, so, when printing
+   line numbers, we allow a gap of up to one line between spans when
+   merging, since it makes more sense to print the source line rather than a
+   "gap-in-line-numbering" line.  When not printing line numbers, it's
+   better to be more explicit about what's going on, so keeping them as
+   separate spans is preferred.
+
+   For example, if the primary range is on lines 8-10, with secondary ranges
+   covering lines 5-6 and lines 13-15:
 
      004
-     005                   |RANGE 0
-     006                   |RANGE 0
-     007  |PRIMARY CARET
-     008
-     009                                |RANGE 1
-     010                                |RANGE 1
-     011                                |RANGE 1
-     012                                |RANGE 1
-     013
-
-   then we want two spans: lines 5-7 and lines 9-12.  */
+     005                   |RANGE 1
+     006                   |RANGE 1
+     007
+     008  |PRIMARY RANGE
+     009  |PRIMARY CARET
+     010  |PRIMARY RANGE
+     011
+     012
+     013                                |RANGE 2
+     014                                |RANGE 2
+     015                                |RANGE 2
+     016
+
+   With line numbering on, we want two spans: lines 5-10 and lines 13-15.
+
+   With line numbering off (with span headers), we want three spans: lines 5-6,
+   lines 8-10, and lines 13-15.  */
 
 void
 layout::calculate_line_spans ()
@@ -1030,7 +1312,9 @@ layout::calculate_line_spans ()
       line_span *current = &m_line_spans[m_line_spans.length () - 1];
       const line_span *next = &tmp_spans[i];
       gcc_assert (next->m_first_line >= current->m_first_line);
-      if (next->m_first_line <= current->m_last_line + 1)
+      const int merger_distance = m_show_line_numbers_p ? 1 : 0;
+      if ((linenum_arith_t)next->m_first_line
+         <= (linenum_arith_t)current->m_last_line + 1 + merger_distance)
        {
          /* We can merge them. */
          if (next->m_last_line > current->m_last_line)
@@ -1059,28 +1343,156 @@ layout::calculate_line_spans ()
     }
 }
 
+/* Determine how many display columns need to be reserved for line numbers,
+   based on the largest line number that will be needed, and populate
+   m_linenum_width.  */
+
+void
+layout::calculate_linenum_width ()
+{
+  gcc_assert (m_line_spans.length () > 0);
+  const line_span *last_span = &m_line_spans[m_line_spans.length () - 1];
+  int highest_line = last_span->m_last_line;
+  if (highest_line < 0)
+    highest_line = 0;
+  m_linenum_width = num_digits (highest_line);
+  /* If we're showing jumps in the line-numbering, allow at least 3 chars.  */
+  if (m_line_spans.length () > 1)
+    m_linenum_width = MAX (m_linenum_width, 3);
+  /* If there's a minimum margin width, apply it (subtracting 1 for the space
+     after the line number.  */
+  m_linenum_width = MAX (m_linenum_width, m_context->min_margin_width - 1);
+}
+
+/* Calculate m_x_offset_display, which improves readability in case the source
+   line of interest is longer than the user's display.  All lines output will be
+   shifted to the left (so that their beginning is no longer displayed) by
+   m_x_offset_display display columns, so that the caret is in a reasonable
+   location.  */
+
+void
+layout::calculate_x_offset_display ()
+{
+  m_x_offset_display = 0;
+
+  const int max_width = m_context->caret_max_width;
+  if (!max_width)
+    {
+      /* Nothing to do, the width is not capped.  */
+      return;
+    }
+
+  const char_span line = location_get_source_line (m_exploc.file,
+                                                  m_exploc.line);
+  if (!line)
+    {
+      /* Nothing to do, we couldn't find the source line.  */
+      return;
+    }
+  int caret_display_column = m_exploc.m_display_col;
+  const int line_bytes
+    = get_line_bytes_without_trailing_whitespace (line.get_buffer (),
+                                                 line.length ());
+  int eol_display_column
+    = cpp_display_width (line.get_buffer (), line_bytes);
+  if (caret_display_column > eol_display_column
+      || !caret_display_column)
+    {
+      /* This does not make sense, so don't try to do anything in this case.  */
+      return;
+    }
+
+  /* Adjust caret and eol positions to include the left margin.  If we are
+     outputting line numbers, then the left margin is equal to m_linenum_width
+     plus three for the " | " which follows it.  Otherwise the left margin width
+     is equal to 1, because layout::print_source_line() will prefix each line
+     with a space.  */
+  const int source_display_cols = eol_display_column;
+  int left_margin_size = 1;
+  if (m_show_line_numbers_p)
+      left_margin_size = m_linenum_width + 3;
+  caret_display_column += left_margin_size;
+  eol_display_column += left_margin_size;
+
+  if (eol_display_column <= max_width)
+    {
+      /* Nothing to do, everything fits in the display.  */
+      return;
+    }
+
+  /* The line is too long for the display.  Calculate an offset such that the
+     caret is not too close to the right edge of the screen.  It will be
+     CARET_LINE_MARGIN display columns from the right edge, unless it is closer
+     than that to the end of the source line anyway.  */
+  int right_margin_size = CARET_LINE_MARGIN;
+  right_margin_size = MIN (eol_display_column - caret_display_column,
+                          right_margin_size);
+  if (right_margin_size + left_margin_size >= max_width)
+    {
+      /* The max_width is very small, so anything we try to do will not be very
+        effective; just punt in this case and output with no offset.  */
+      return;
+    }
+  const int max_caret_display_column = max_width - right_margin_size;
+  if (caret_display_column > max_caret_display_column)
+    {
+      m_x_offset_display = caret_display_column - max_caret_display_column;
+      /* Make sure we don't offset the line into oblivion.  */
+      static const int min_cols_visible = 2;
+      if (source_display_cols - m_x_offset_display < min_cols_visible)
+       m_x_offset_display = 0;
+    }
+}
+
 /* Print line ROW of source code, potentially colorized at any ranges, and
    populate *LBOUNDS_OUT.
-   LINE is the source line (not necessarily 0-terminated) and LINE_WIDTH
-   is its width.  */
+   LINE is the source line (not necessarily 0-terminated) and LINE_BYTES
+   is its length in bytes.
+   This function deals only with byte offsets, not display columns, so
+   m_x_offset_display must be converted from display to byte units.  In
+   particular, LINE_BYTES and LBOUNDS_OUT are in bytes.  */
 
 void
-layout::print_source_line (int row, const char *line, int line_width,
+layout::print_source_line (linenum_type row, const char *line, int line_bytes,
                           line_bounds *lbounds_out)
 {
   m_colorizer.set_normal_text ();
 
-  /* We will stop printing the source line at any trailing
-     whitespace.  */
-  line_width = get_line_width_without_trailing_whitespace (line,
-                                                          line_width);
-  line += m_x_offset;
+  pp_emit_prefix (m_pp);
+  if (m_show_line_numbers_p)
+    {
+      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);
+    }
+  else
+    pp_space (m_pp);
+
+  /* We will stop printing the source line at any trailing whitespace, and start
+     printing it as per m_x_offset_display.  */
+  line_bytes = get_line_bytes_without_trailing_whitespace (line,
+                                                          line_bytes);
+  int x_offset_bytes = 0;
+  if (m_x_offset_display)
+    {
+      x_offset_bytes = cpp_display_column_to_byte_column (line, line_bytes,
+                                                         m_x_offset_display);
+      /* In case the leading portion of the line that will be skipped over ends
+        with a character with wcwidth > 1, then it is possible we skipped too
+        much, so account for that by padding with spaces.  */
+      const int overage
+       = cpp_byte_column_to_display_column (line, line_bytes, x_offset_bytes)
+       - m_x_offset_display;
+      for (int column = 0; column < overage; ++column)
+       pp_space (m_pp);
+      line += x_offset_bytes;
+    }
 
-  pp_space (m_pp);
+  /* Print the line.  */
   int first_non_ws = INT_MAX;
   int last_non_ws = 0;
-  int column;
-  for (column = 1 + m_x_offset; column <= line_width; column++)
+  for (int col_byte = 1 + x_offset_bytes; col_byte <= line_bytes; col_byte++)
     {
       /* Assuming colorization is enabled for the caret and underline
         characters, we may also colorize the associated characters
@@ -1098,22 +1510,23 @@ layout::print_source_line (int row, const char *line, int line_width,
        {
          bool in_range_p;
          point_state state;
-         in_range_p = get_state_at_point (row, column,
+         in_range_p = get_state_at_point (row, col_byte,
                                           0, INT_MAX,
+                                          CU_BYTES,
                                           &state);
          if (in_range_p)
            m_colorizer.set_range (state.range_idx);
          else
            m_colorizer.set_normal_text ();
        }
-      char c = *line == '\t' ? ' ' : *line;
-      if (c == '\0')
+      char c = *line;
+      if (c == '\0' || c == '\t' || c == '\r')
        c = ' ';
       if (c != ' ')
        {
-         last_non_ws = column;
+         last_non_ws = col_byte;
          if (first_non_ws == INT_MAX)
-           first_non_ws = column;
+           first_non_ws = col_byte;
        }
       pp_character (m_pp, c);
       line++;
@@ -1128,33 +1541,61 @@ layout::print_source_line (int row, const char *line, int line_width,
    i.e. if any of m_layout_ranges contains ROW.  */
 
 bool
-layout::should_print_annotation_line_p (int row) const
+layout::should_print_annotation_line_p (linenum_type row) const
 {
   layout_range *range;
   int i;
   FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
-    if (range->intersects_line_p (row))
-      return true;
+    {
+      if (range->m_range_display_kind == SHOW_LINES_WITHOUT_RANGE)
+       return false;
+      if (range->intersects_line_p (row))
+       return true;
+    }
   return false;
 }
 
+/* Begin an annotation line.  If m_show_line_numbers_p, print the left
+   margin, which is empty for annotation lines.  Otherwise, do nothing.  */
+
+void
+layout::start_annotation_line (char margin_char) const
+{
+  pp_emit_prefix (m_pp);
+  if (m_show_line_numbers_p)
+    {
+      /* Print the margin.  If MARGIN_CHAR != ' ', then print up to 3
+        of it, right-aligned, padded with spaces.  */
+      int i;
+      for (i = 0; i < m_linenum_width - 3; i++)
+       pp_space (m_pp);
+      for (; i < m_linenum_width; i++)
+       pp_character (m_pp, margin_char);
+      pp_string (m_pp, " |");
+    }
+}
+
 /* Print a line consisting of the caret/underlines for the given
-   source line.  */
+   source line.  This function works with display columns, rather than byte
+   counts; in particular, LBOUNDS should be in display column units.  */
 
 void
-layout::print_annotation_line (int row, const line_bounds lbounds)
+layout::print_annotation_line (linenum_type row, const line_bounds lbounds)
 {
-  int x_bound = get_x_bound_for_row (row, m_exploc.column,
+  int x_bound = get_x_bound_for_row (row, m_exploc.m_display_col,
                                     lbounds.m_last_non_ws);
 
+  start_annotation_line ();
   pp_space (m_pp);
-  for (int column = 1 + m_x_offset; column < x_bound; column++)
+
+  for (int column = 1 + m_x_offset_display; column < x_bound; column++)
     {
       bool in_range_p;
       point_state state;
       in_range_p = get_state_at_point (row, column,
                                       lbounds.m_first_non_ws,
                                       lbounds.m_last_non_ws,
+                                      CU_DISPLAY_COLS,
                                       &state);
       if (in_range_p)
        {
@@ -1183,6 +1624,194 @@ layout::print_annotation_line (int row, const line_bounds lbounds)
   print_newline ();
 }
 
+/* Implementation detail of layout::print_any_labels.
+
+   A label within the given row of source.  */
+
+class line_label
+{
+public:
+  line_label (int state_idx, int column, label_text text)
+  : m_state_idx (state_idx), m_column (column),
+    m_text (text), m_label_line (0), m_has_vbar (true)
+  {
+    const int bytes = strlen (text.m_buffer);
+    m_display_width = cpp_display_width (text.m_buffer, bytes);
+  }
+
+  /* Sorting is primarily by column, then by state index.  */
+  static int comparator (const void *p1, const void *p2)
+  {
+    const line_label *ll1 = (const line_label *)p1;
+    const line_label *ll2 = (const line_label *)p2;
+    int column_cmp = compare (ll1->m_column, ll2->m_column);
+    if (column_cmp)
+      return column_cmp;
+    /* Order by reverse state index, so that labels are printed
+       in order of insertion into the rich_location when the
+       sorted list is walked backwards.  */
+    return -compare (ll1->m_state_idx, ll2->m_state_idx);
+  }
+
+  int m_state_idx;
+  int m_column;
+  label_text m_text;
+  size_t m_display_width;
+  int m_label_line;
+  bool m_has_vbar;
+};
+
+/* Print any labels in this row.  */
+void
+layout::print_any_labels (linenum_type row)
+{
+  int i;
+  auto_vec<line_label> labels;
+
+  /* Gather the labels that are to be printed into "labels".  */
+  {
+    layout_range *range;
+    FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
+      {
+       /* Most ranges don't have labels, so reject this first.  */
+       if (range->m_label == NULL)
+         continue;
+
+       /* The range's caret must be on this line.  */
+       if (range->m_caret.m_line != row)
+         continue;
+
+       /* Reject labels that aren't fully visible due to clipping
+          by m_x_offset_display.  */
+       const int disp_col = range->m_caret.m_columns[CU_DISPLAY_COLS];
+       if (disp_col <= m_x_offset_display)
+         continue;
+
+       label_text text;
+       text = range->m_label->get_text (range->m_original_idx);
+
+       /* Allow for labels that return NULL from their get_text
+          implementation (so e.g. such labels can control their own
+          visibility).  */
+       if (text.m_buffer == NULL)
+         continue;
+
+       labels.safe_push (line_label (i, disp_col, text));
+      }
+  }
+
+  /* Bail out if there are no labels on this row.  */
+  if (labels.length () == 0)
+    return;
+
+  /* Sort them.  */
+  labels.qsort(line_label::comparator);
+
+  /* Figure out how many "label lines" we need, and which
+     one each label is printed in.
+
+     For example, if the labels aren't too densely packed,
+     we can fit them on the same line, giving two "label lines":
+
+       foo + bar
+       ~~~   ~~~
+       |     |        : label line 0
+       l0    l1       : label line 1
+
+     If they would touch each other or overlap, then we need
+     additional "label lines":
+
+       foo + bar
+       ~~~   ~~~
+       |     |             : label line 0
+       |     label 1       : label line 1
+       label 0             : label line 2
+
+     Place the final label on label line 1, and work backwards, adding
+     label lines as needed.
+
+     If multiple labels are at the same place, put them on separate
+     label lines:
+
+       foo + bar
+           ^               : label line 0
+           |               : label line 1
+           label 0         : label line 2
+           label 1         : label line 3.  */
+
+  int max_label_line = 1;
+  {
+    int next_column = INT_MAX;
+    line_label *label;
+    FOR_EACH_VEC_ELT_REVERSE (labels, i, label)
+      {
+       /* Would this label "touch" or overlap the next label?  */
+       if (label->m_column + label->m_display_width >= (size_t)next_column)
+         {
+           max_label_line++;
+
+           /* If we've already seen labels with the same column, suppress the
+              vertical bar for subsequent ones in this backwards iteration;
+              hence only the one with the highest label_line has m_has_vbar set.  */
+           if (label->m_column == next_column)
+             label->m_has_vbar = false;
+         }
+
+       label->m_label_line = max_label_line;
+       next_column = label->m_column;
+      }
+  }
+
+  /* Print the "label lines".  For each label within the line, print
+     either a vertical bar ('|') for the labels that are lower down, or the
+     labels themselves once we've reached their line.  */
+  {
+    for (int label_line = 0; label_line <= max_label_line; label_line++)
+      {
+       start_annotation_line ();
+       pp_space (m_pp);
+       int column = 1 + m_x_offset_display;
+       line_label *label;
+       FOR_EACH_VEC_ELT (labels, i, label)
+         {
+           if (label_line > label->m_label_line)
+             /* We've printed all the labels for this label line.  */
+             break;
+
+           if (label_line == label->m_label_line)
+             {
+               gcc_assert (column <= label->m_column);
+               move_to_column (&column, label->m_column, true);
+               /* Colorize the text, unless it's for events in a
+                  diagnostic_path.  */
+               if (!m_diagnostic_path_p)
+                 m_colorizer.set_range (label->m_state_idx);
+               pp_string (m_pp, label->m_text.m_buffer);
+               m_colorizer.set_normal_text ();
+               column += label->m_display_width;
+             }
+           else if (label->m_has_vbar)
+             {
+               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, '|');
+               m_colorizer.set_normal_text ();
+               column++;
+             }
+         }
+       print_newline ();
+      }
+    }
+
+  /* Clean up.  */
+  {
+    line_label *label;
+    FOR_EACH_VEC_ELT (labels, i, label)
+      label->m_text.maybe_free ();
+  }
+}
+
 /* If there are any fixit hints inserting new lines before source line ROW,
    print them.
 
@@ -1190,7 +1819,7 @@ layout::print_annotation_line (int row, const line_bounds lbounds)
    itself, with a leading '+'.  */
 
 void
-layout::print_leading_fixits (int row)
+layout::print_leading_fixits (linenum_type row)
 {
   for (unsigned int i = 0; i < m_fixit_hints.length (); i++)
     {
@@ -1209,6 +1838,7 @@ layout::print_leading_fixits (int row)
             helps them stand out from each other, and from
             the surrounding text.  */
          m_colorizer.set_normal_text ();
+         start_annotation_line ('+');
          pp_character (m_pp, '+');
          m_colorizer.set_fixit_insert ();
          /* Print all but the trailing newline of the fix-it hint.
@@ -1225,19 +1855,19 @@ layout::print_leading_fixits (int row)
 /* Subroutine of layout::print_trailing_fixits.
 
    Determine if the annotation line printed for LINE contained
-   the exact range from START_COLUMN to FINISH_COLUMN.  */
+   the exact range from START_COLUMN to FINISH_COLUMN (in display units).  */
 
 bool
-layout::annotation_line_showed_range_p (int line, int start_column,
+layout::annotation_line_showed_range_p (linenum_type line, int start_column,
                                        int finish_column) const
 {
   layout_range *range;
   int i;
   FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
     if (range->m_start.m_line == line
-       && range->m_start.m_column == start_column
+       && range->m_start.m_columns[CU_DISPLAY_COLS] == start_column
        && range->m_finish.m_line == line
-       && range->m_finish.m_column == finish_column)
+       && range->m_finish.m_columns[CU_DISPLAY_COLS] == finish_column)
       return true;
   return false;
 }
@@ -1324,11 +1954,16 @@ layout::annotation_line_showed_range_p (int line, int start_column,
 
    and is thus printed as desired.  */
 
-/* A range of columns within a line.  */
+/* A range of (byte or display) columns within a line.  */
 
-struct column_range
+class column_range
 {
-  column_range (int start_, int finish_) : start (start_), finish (finish_) {}
+public:
+  column_range (int start_, int finish_) : start (start_), finish (finish_)
+  {
+    /* We must have either a range, or an insertion.  */
+    gcc_assert (start <= finish || finish == start - 1);
+  }
 
   bool operator== (const column_range &other) const
   {
@@ -1339,32 +1974,51 @@ struct column_range
   int finish;
 };
 
-/* Get the range of columns that HINT would affect.  */
-
+/* Get the range of bytes or display columns that HINT would affect.  */
 static column_range
-get_affected_columns (const fixit_hint *hint)
+get_affected_range (const fixit_hint *hint, enum column_unit col_unit)
 {
-  int start_column = LOCATION_COLUMN (hint->get_start_loc ());
-  int finish_column = LOCATION_COLUMN (hint->get_next_loc ()) - 1;
+  expanded_location exploc_start = expand_location (hint->get_start_loc ());
+  expanded_location exploc_finish = expand_location (hint->get_next_loc ());
+  --exploc_finish.column;
 
+  int start_column;
+  int finish_column;
+  if (col_unit == CU_DISPLAY_COLS)
+    {
+      start_column = location_compute_display_column (exploc_start);
+      if (hint->insertion_p ())
+       finish_column = start_column - 1;
+      else
+       finish_column = location_compute_display_column (exploc_finish);
+    }
+  else
+    {
+      start_column = exploc_start.column;
+      finish_column = exploc_finish.column;
+    }
   return column_range (start_column, finish_column);
 }
 
-/* Get the range of columns that would be printed for HINT.  */
+/* Get the range of display columns that would be printed for HINT.  */
 
 static column_range
 get_printed_columns (const fixit_hint *hint)
 {
-  int start_column = LOCATION_COLUMN (hint->get_start_loc ());
-  int final_hint_column = start_column + hint->get_length () - 1;
+  expanded_location exploc = expand_location (hint->get_start_loc ());
+  int start_column = location_compute_display_column (exploc);
+  int hint_width = cpp_display_width (hint->get_string (),
+                                     hint->get_length ());
+  int final_hint_column = start_column + hint_width - 1;
   if (hint->insertion_p ())
     {
       return column_range (start_column, final_hint_column);
     }
   else
     {
-      int finish_column = LOCATION_COLUMN (hint->get_next_loc ()) - 1;
-
+      exploc = expand_location (hint->get_next_loc ());
+      --exploc.column;
+      int finish_column = location_compute_display_column (exploc);
       return column_range (start_column,
                           MAX (finish_column, final_hint_column));
     }
@@ -1375,33 +2029,51 @@ get_printed_columns (const fixit_hint *hint)
    instances that affected the line, potentially consolidating hints
    into corrections to make the result easier for the user to read.  */
 
-struct correction
+class correction
 {
-  correction (column_range affected_columns,
+public:
+  correction (column_range affected_bytes,
+             column_range affected_columns,
              column_range printed_columns,
              const char *new_text, size_t new_text_len)
-  : m_affected_columns (affected_columns),
+  : m_affected_bytes (affected_bytes),
+    m_affected_columns (affected_columns),
     m_printed_columns (printed_columns),
     m_text (xstrdup (new_text)),
-    m_len (new_text_len),
+    m_byte_length (new_text_len),
     m_alloc_sz (new_text_len + 1)
   {
+    compute_display_cols ();
   }
 
   ~correction () { free (m_text); }
 
   bool insertion_p () const
   {
-    return m_affected_columns.start == m_affected_columns.finish + 1;
+    return m_affected_bytes.start == m_affected_bytes.finish + 1;
   }
 
   void ensure_capacity (size_t len);
   void ensure_terminated ();
 
+  void compute_display_cols ()
+  {
+    m_display_cols = cpp_display_width (m_text, m_byte_length);
+  }
+
+  void overwrite (int dst_offset, const char_span &src_span)
+  {
+    gcc_assert (dst_offset >= 0);
+    gcc_assert (dst_offset + src_span.length () < m_alloc_sz);
+    memcpy (m_text + dst_offset, src_span.get_buffer (),
+           src_span.length ());
+  }
+
   /* If insert, then start: the column before which the text
      is to be inserted, and finish is offset by the length of
      the replacement.
      If replace, then the range of columns affected.  */
+  column_range m_affected_bytes;
   column_range m_affected_columns;
 
   /* If insert, then start: the column before which the text
@@ -1412,7 +2084,8 @@ struct correction
 
   /* The text to be inserted/used as replacement.  */
   char *m_text;
-  size_t m_len;
+  size_t m_byte_length; /* Not including null-terminator.  */
+  int m_display_cols;
   size_t m_alloc_sz;
 };
 
@@ -1437,17 +2110,18 @@ void
 correction::ensure_terminated ()
 {
   /* 0-terminate the buffer.  */
-  gcc_assert (m_len < m_alloc_sz);
-  m_text[m_len] = '\0';
+  gcc_assert (m_byte_length < m_alloc_sz);
+  m_text[m_byte_length] = '\0';
 }
 
 /* A list of corrections affecting a particular line.
    This is used by layout::print_trailing_fixits for planning
    how to print the fix-it hints affecting the line.  */
 
-struct line_corrections
+class line_corrections
 {
-  line_corrections (const char *filename, int row)
+public:
+  line_corrections (const char *filename, linenum_type row)
   : m_filename (filename), m_row (row)
   {}
   ~line_corrections ();
@@ -1455,7 +2129,7 @@ struct line_corrections
   void add_hint (const fixit_hint *hint);
 
   const char *m_filename;
-  int m_row;
+  linenum_type m_row;
   auto_vec <correction *> m_corrections;
 };
 
@@ -1469,6 +2143,29 @@ line_corrections::~line_corrections ()
     delete c;
 }
 
+/* A struct wrapping a particular source line, allowing
+   run-time bounds-checking of accesses in a checked build.  */
+
+class source_line
+{
+public:
+  source_line (const char *filename, int line);
+
+  char_span as_span () { return char_span (chars, width); }
+
+  const char *chars;
+  int width;
+};
+
+/* source_line's ctor.  */
+
+source_line::source_line (const char *filename, int line)
+{
+  char_span span = location_get_source_line (filename, line);
+  chars = span.get_buffer ();
+  width = span.length ();
+}
+
 /* Add HINT to the corrections for this line.
    Attempt to consolidate nearby hints so that they will not
    overlap with printed.  */
@@ -1476,7 +2173,8 @@ line_corrections::~line_corrections ()
 void
 line_corrections::add_hint (const fixit_hint *hint)
 {
-  column_range affected_columns = get_affected_columns (hint);
+  column_range affected_bytes = get_affected_range (hint, CU_BYTES);
+  column_range affected_columns = get_affected_range (hint, CU_DISPLAY_COLS);
   column_range printed_columns = get_printed_columns (hint);
 
   /* Potentially consolidate.  */
@@ -1484,6 +2182,14 @@ line_corrections::add_hint (const fixit_hint *hint)
     {
       correction *last_correction
        = m_corrections[m_corrections.length () - 1];
+
+      /* The following consolidation code assumes that the fix-it hints
+        have been sorted by start (done within layout's ctor).  */
+      gcc_assert (affected_bytes.start
+                 >= last_correction->m_affected_bytes.start);
+      gcc_assert (printed_columns.start
+                 >= last_correction->m_printed_columns.start);
+
       if (printed_columns.start <= last_correction->m_printed_columns.finish)
        {
          /* We have two hints for which the printed forms of the hints
@@ -1492,40 +2198,49 @@ line_corrections::add_hint (const fixit_hint *hint)
             Attempt to inject a "replace" correction from immediately
             after the end of the last hint to immediately before the start
             of the next hint.  */
-         column_range between (last_correction->m_affected_columns.finish + 1,
-                               printed_columns.start - 1);
+         column_range between (last_correction->m_affected_bytes.finish + 1,
+                               affected_bytes.start - 1);
 
          /* Try to read the source.  */
-         int line_width;
-         const char *line = location_get_source_line (m_filename, m_row,
-                                                      &line_width);
-         if (line && between.finish < line_width)
+         source_line line (m_filename, m_row);
+         if (line.chars && between.finish < line.width)
            {
              /* Consolidate into the last correction:
                 add a no-op "replace" of the "between" text, and
                 add the text from the new hint.  */
-             size_t old_len = last_correction->m_len;
-             size_t between_len = between.finish + 1 - between.start;
-             size_t new_len = old_len + between_len + hint->get_length ();
-             last_correction->ensure_capacity (new_len);
-             memcpy (last_correction->m_text + old_len,
-                     line + between.start - 1,
-                     between.finish + 1 - between.start);
-             memcpy (last_correction->m_text + old_len + between_len,
-                     hint->get_string (), hint->get_length ());
-             last_correction->m_len = new_len;
+             int old_byte_len = last_correction->m_byte_length;
+             gcc_assert (old_byte_len >= 0);
+             int between_byte_len = between.finish + 1 - between.start;
+             gcc_assert (between_byte_len >= 0);
+             int new_byte_len
+               = old_byte_len + between_byte_len + hint->get_length ();
+             gcc_assert (new_byte_len >= 0);
+             last_correction->ensure_capacity (new_byte_len);
+             last_correction->overwrite
+               (old_byte_len,
+                line.as_span ().subspan (between.start - 1,
+                                         between.finish + 1 - between.start));
+             last_correction->overwrite (old_byte_len + between_byte_len,
+                                         char_span (hint->get_string (),
+                                                    hint->get_length ()));
+             last_correction->m_byte_length = new_byte_len;
              last_correction->ensure_terminated ();
+             last_correction->m_affected_bytes.finish
+               = affected_bytes.finish;
              last_correction->m_affected_columns.finish
                = affected_columns.finish;
+             int prev_display_cols = last_correction->m_display_cols;
+             last_correction->compute_display_cols ();
              last_correction->m_printed_columns.finish
-               += between_len + hint->get_length ();
+               += last_correction->m_display_cols - prev_display_cols;
              return;
            }
        }
     }
 
   /* If no consolidation happened, add a new correction instance.  */
-  m_corrections.safe_push (new correction (affected_columns,
+  m_corrections.safe_push (new correction (affected_bytes,
+                                          affected_columns,
                                           printed_columns,
                                           hint->get_string (),
                                           hint->get_length ()));
@@ -1538,7 +2253,7 @@ line_corrections::add_hint (const fixit_hint *hint)
    in layout::print_leading_fixits.  */
 
 void
-layout::print_trailing_fixits (int row)
+layout::print_trailing_fixits (linenum_type row)
 {
   /* Build a list of correction instances for the line,
      potentially consolidating hints (for the sake of readability).  */
@@ -1558,7 +2273,10 @@ layout::print_trailing_fixits (int row)
   /* Now print the corrections.  */
   unsigned i;
   correction *c;
-  int column = 0;
+  int column = m_x_offset_display;
+
+  if (!corrections.m_corrections.is_empty ())
+    start_annotation_line ();
 
   FOR_EACH_VEC_ELT (corrections.m_corrections, i, c)
     {
@@ -1567,11 +2285,11 @@ layout::print_trailing_fixits (int row)
        {
          /* This assumes the insertion just affects one line.  */
          int start_column = c->m_printed_columns.start;
-         move_to_column (&column, start_column);
+         move_to_column (&column, start_column, true);
          m_colorizer.set_fixit_insert ();
          pp_string (m_pp, c->m_text);
          m_colorizer.set_normal_text ();
-         column += c->m_len;
+         column += c->m_display_cols;
        }
       else
        {
@@ -1583,9 +2301,9 @@ layout::print_trailing_fixits (int row)
          int finish_column = c->m_affected_columns.finish;
          if (!annotation_line_showed_range_p (row, start_column,
                                               finish_column)
-             || c->m_len == 0)
+             || c->m_byte_length == 0)
            {
-             move_to_column (&column, start_column);
+             move_to_column (&column, start_column, true);
              m_colorizer.set_fixit_delete ();
              for (; column <= finish_column; column++)
                pp_character (m_pp, '-');
@@ -1594,19 +2312,19 @@ layout::print_trailing_fixits (int row)
          /* Print the replacement text.  REPLACE also covers
             removals, so only do this extra work (potentially starting
             a new line) if we have actual replacement text.  */
-         if (c->m_len > 0)
+         if (c->m_byte_length > 0)
            {
-             move_to_column (&column, start_column);
+             move_to_column (&column, start_column, true);
              m_colorizer.set_fixit_insert ();
              pp_string (m_pp, c->m_text);
              m_colorizer.set_normal_text ();
-             column += c->m_len;
+             column += c->m_display_cols;
            }
        }
     }
 
   /* Add a trailing newline, if necessary.  */
-  move_to_column (&column, 0);
+  move_to_column (&column, 0, false);
 }
 
 /* Disable any colorization and emit a newline.  */
@@ -1621,12 +2339,14 @@ layout::print_newline ()
 /* Return true if (ROW/COLUMN) is within a range of the layout.
    If it returns true, OUT_STATE is written to, with the
    range index, and whether we should draw the caret at
-   (ROW/COLUMN) (as opposed to an underline).  */
+   (ROW/COLUMN) (as opposed to an underline).  COL_UNIT controls
+   whether all inputs and outputs are in bytes or display column units.  */
 
 bool
 layout::get_state_at_point (/* Inputs.  */
-                           int row, int column,
+                           linenum_type row, int column,
                            int first_non_ws, int last_non_ws,
+                           enum column_unit col_unit,
                            /* Outputs.  */
                            point_state *out_state)
 {
@@ -1634,15 +2354,20 @@ layout::get_state_at_point (/* Inputs.  */
   int i;
   FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
     {
-      if (range->contains_point (row, column))
+      if (range->m_range_display_kind == SHOW_LINES_WITHOUT_RANGE)
+       /* Bail out early, so that such ranges don't affect underlining or
+          source colorization.  */
+       continue;
+
+      if (range->contains_point (row, column, col_unit))
        {
          out_state->range_idx = i;
 
          /* Are we at the range's caret?  is it visible? */
          out_state->draw_caret_p = false;
-         if (range->m_show_caret_p
+         if (range->m_range_display_kind == SHOW_RANGE_WITH_CARET
              && row == range->m_caret.m_line
-             && column == range->m_caret.m_column)
+             && column == range->m_caret.m_columns[col_unit])
            out_state->draw_caret_p = true;
 
          /* Within a multiline range, don't display any underline
@@ -1662,15 +2387,15 @@ layout::get_state_at_point (/* Inputs.  */
 
 /* Helper function for use by layout::print_line when printing the
    annotation line under the source line.
-   Get the column beyond the rightmost one that could contain a caret or
-   range marker, given that we stop rendering at trailing whitespace.
+   Get the display column beyond the rightmost one that could contain a caret
+   or range marker, given that we stop rendering at trailing whitespace.
    ROW is the source line within the given file.
-   CARET_COLUMN is the column of range 0's caret.
-   LAST_NON_WS_COLUMN is the last column containing a non-whitespace
+   CARET_COLUMN is the display column of range 0's caret.
+   LAST_NON_WS_COLUMN is the last display column containing a non-whitespace
    character of source (as determined when printing the source line).  */
 
 int
-layout::get_x_bound_for_row (int row, int caret_column,
+layout::get_x_bound_for_row (linenum_type row, int caret_column,
                             int last_non_ws_column)
 {
   int result = caret_column + 1;
@@ -1685,8 +2410,9 @@ layout::get_x_bound_for_row (int row, int caret_column,
            {
              /* On the final line within a range; ensure that
                 we render up to the end of the range.  */
-             if (result <= range->m_finish.m_column)
-               result = range->m_finish.m_column + 1;
+             const int disp_col = range->m_finish.m_columns[CU_DISPLAY_COLS];
+             if (result <= disp_col)
+               result = disp_col + 1;
            }
          else if (row < range->m_finish.m_line)
            {
@@ -1703,16 +2429,19 @@ layout::get_x_bound_for_row (int row, int caret_column,
 
 /* Given *COLUMN as an x-coordinate, print spaces to position
    successive output at DEST_COLUMN, printing a newline if necessary,
-   and updating *COLUMN.  */
+   and updating *COLUMN.  If ADD_LEFT_MARGIN, then print the (empty)
+   left margin after any newline.  */
 
 void
-layout::move_to_column (int *column, int dest_column)
+layout::move_to_column (int *column, int dest_column, bool add_left_margin)
 {
   /* Start a new line if we need to.  */
   if (*column > dest_column)
     {
       print_newline ();
-      *column = 0;
+      if (add_left_margin)
+       start_annotation_line ();
+      *column = m_x_offset_display;
     }
 
   while (*column < dest_column)
@@ -1731,9 +2460,10 @@ layout::show_ruler (int max_column) const
   /* Hundreds.  */
   if (max_column > 99)
     {
+      start_annotation_line ();
       pp_space (m_pp);
-      for (int column = 1 + m_x_offset; column <= max_column; column++)
-       if (0 == column % 10)
+      for (int column = 1 + m_x_offset_display; column <= max_column; column++)
+       if (column % 10 == 0)
          pp_character (m_pp, '0' + (column / 100) % 10);
        else
          pp_space (m_pp);
@@ -1741,17 +2471,19 @@ layout::show_ruler (int max_column) const
     }
 
   /* Tens.  */
+  start_annotation_line ();
   pp_space (m_pp);
-  for (int column = 1 + m_x_offset; column <= max_column; column++)
-    if (0 == column % 10)
+  for (int column = 1 + m_x_offset_display; column <= max_column; column++)
+    if (column % 10 == 0)
       pp_character (m_pp, '0' + (column / 10) % 10);
     else
       pp_space (m_pp);
   pp_newline (m_pp);
 
   /* Units.  */
+  start_annotation_line ();
   pp_space (m_pp);
-  for (int column = 1 + m_x_offset; column <= max_column; column++)
+  for (int column = 1 + m_x_offset_display; column <= max_column; column++)
     pp_character (m_pp, '0' + (column % 10));
   pp_newline (m_pp);
 }
@@ -1761,34 +2493,61 @@ layout::show_ruler (int max_column) const
    consisting of any caret/underlines, then any fixits.
    If the source line can't be read, print nothing.  */
 void
-layout::print_line (int row)
+layout::print_line (linenum_type row)
 {
-  int line_width;
-  const char *line = location_get_source_line (m_exploc.file, row,
-                                              &line_width);
+  char_span line = location_get_source_line (m_exploc.file, row);
   if (!line)
     return;
 
   line_bounds lbounds;
   print_leading_fixits (row);
-  print_source_line (row, line, line_width, &lbounds);
+  print_source_line (row, line.get_buffer (), line.length (), &lbounds);
   if (should_print_annotation_line_p (row))
-    print_annotation_line (row, lbounds);
+    {
+      if (lbounds.m_first_non_ws != INT_MAX)
+       lbounds.convert_to_display_cols (line);
+      print_annotation_line (row, lbounds);
+    }
+  if (m_show_labels_p)
+    print_any_labels (row);
   print_trailing_fixits (row);
 }
 
 } /* End of anonymous namespace.  */
 
-/* Print the physical source code corresponding to the location of
-   this diagnostic, with additional annotations.  */
+/* If LOC is within the spans of lines that will already be printed for
+   this gcc_rich_location, then add it as a secondary location and return true.
 
-void
-diagnostic_show_locus (diagnostic_context * context,
-                      rich_location *richloc,
-                      diagnostic_t diagnostic_kind)
-{
-  pp_newline (context->printer);
+   Otherwise return false.  */
 
+bool
+gcc_rich_location::add_location_if_nearby (location_t loc,
+                                          bool restrict_to_current_line_spans,
+                                          const range_label *label)
+{
+  /* Use the layout location-handling logic to sanitize LOC,
+     filtering it to the current line spans within a temporary
+     layout instance.  */
+  layout layout (global_dc, this, DK_ERROR);
+  location_range loc_range;
+  loc_range.m_loc = loc;
+  loc_range.m_range_display_kind = SHOW_RANGE_WITHOUT_CARET;
+  if (!layout.maybe_add_location_range (&loc_range, 0,
+                                       restrict_to_current_line_spans))
+    return false;
+
+  add_range (loc, SHOW_RANGE_WITHOUT_CARET, label);
+  return true;
+}
+
+/* Print the physical source code corresponding to the location of
+   this diagnostic, with additional annotations.  */
+
+void
+diagnostic_show_locus (diagnostic_context * context,
+                      rich_location *richloc,
+                      diagnostic_t diagnostic_kind)
+{
   location_t loc = richloc->get_loc ();
   /* Do nothing if source-printing has been disabled.  */
   if (!context->show_caret)
@@ -1806,25 +2565,35 @@ diagnostic_show_locus (diagnostic_context * context,
 
   context->last_location = loc;
 
-  const char *saved_prefix = pp_get_prefix (context->printer);
-  pp_set_prefix (context->printer, NULL);
-
   layout layout (context, richloc, diagnostic_kind);
   for (int line_span_idx = 0; line_span_idx < layout.get_num_line_spans ();
        line_span_idx++)
     {
       const line_span *line_span = layout.get_line_span (line_span_idx);
-      if (layout.print_heading_for_line_span_index_p (line_span_idx))
+      if (context->show_line_numbers_p)
        {
-         expanded_location exploc = layout.get_expanded_location (line_span);
-         context->start_span (context, exploc);
+         /* With line numbers, we should show whenever the line-numbering
+            "jumps".  */
+         if (line_span_idx > 0)
+           layout.print_gap_in_line_numbering ();
+       }
+      else
+       {
+         /* Without line numbers, we print headings for some line spans.  */
+         if (layout.print_heading_for_line_span_index_p (line_span_idx))
+           {
+             expanded_location exploc
+               = layout.get_expanded_location (line_span);
+             context->start_span (context, exploc);
+           }
        }
-      int last_line = line_span->get_last_line ();
-      for (int row = line_span->get_first_line (); row <= last_line; row++)
+      /* Iterate over the lines within this span (using linenum_arith_t to
+        avoid overflow with 0xffffffff causing an infinite loop).  */
+      linenum_arith_t last_line = line_span->get_last_line ();
+      for (linenum_arith_t row = line_span->get_first_line ();
+          row <= last_line; row++)
        layout.print_line (row);
     }
-
-  pp_set_prefix (context->printer, saved_prefix);
 }
 
 #if CHECKING_P
@@ -1833,33 +2602,177 @@ namespace selftest {
 
 /* Selftests for diagnostic_show_locus.  */
 
-/* Convenience subclass of diagnostic_context for testing
-   diagnostic_show_locus.  */
+/* For precise tests of the layout, make clear where the source line will
+   start.  test_left_margin sets the total byte count from the left side of the
+   screen to the start of source lines, after the line number and the separator,
+   which consists of the three characters " | ".  */
+static const int test_linenum_sep = 3;
+static const int test_left_margin = 7;
 
-class test_diagnostic_context : public diagnostic_context
+/* Helper function for test_layout_x_offset_display_utf8().  */
+static void
+test_offset_impl (int caret_byte_col, int max_width,
+                 int expected_x_offset_display,
+                 int left_margin = test_left_margin)
 {
- public:
-  test_diagnostic_context ()
-  {
-    diagnostic_initialize (this, 0);
-    show_caret = true;
-    show_column = true;
-    start_span = start_span_cb;
-  }
-  ~test_diagnostic_context ()
+  test_diagnostic_context dc;
+  dc.caret_max_width = max_width;
+  /* diagnostic_context::min_margin_width sets the minimum space reserved for
+     the line number plus one space after.  */
+  dc.min_margin_width = left_margin - test_linenum_sep + 1;
+  dc.show_line_numbers_p = true;
+  rich_location richloc (line_table,
+                        linemap_position_for_column (line_table,
+                                                     caret_byte_col));
+  layout test_layout (&dc, &richloc, DK_ERROR);
+  ASSERT_EQ (left_margin - test_linenum_sep,
+            test_layout.get_linenum_width ());
+  ASSERT_EQ (expected_x_offset_display,
+            test_layout.get_x_offset_display ());
+}
+
+/* Test that layout::calculate_x_offset_display() works.  */
+static void
+test_layout_x_offset_display_utf8 (const line_table_case &case_)
+{
+
+  const char *content
+    = "This line is very long, so that we can use it to test the logic for "
+      "clipping long lines.  Also this: \xf0\x9f\x98\x82\xf0\x9f\x98\x82 is a "
+      "pair of emojis that occupies 8 bytes and 4 display columns, starting at "
+      "column #102.\n";
+
+  /* Number of bytes in the line, subtracting one to remove the newline.  */
+  const int line_bytes = strlen (content) - 1;
+
+  /* Number of display columns occupied by the line; each of the 2 emojis
+     takes up 2 fewer display columns than it does bytes.  */
+  const int line_display_cols = line_bytes - 2*2;
+
+  /* The column of the first emoji.  Byte or display is the same as there are
+     no multibyte characters earlier on the line.  */
+  const int emoji_col = 102;
+
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  line_table_test ltt (case_);
+
+  linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1);
+
+  location_t line_end = linemap_position_for_column (line_table, line_bytes);
+
+  /* Don't attempt to run the tests if column data might be unavailable.  */
+  if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
+    return;
+
+  ASSERT_STREQ (tmp.get_filename (), LOCATION_FILE (line_end));
+  ASSERT_EQ (1, LOCATION_LINE (line_end));
+  ASSERT_EQ (line_bytes, LOCATION_COLUMN (line_end));
+
+  char_span lspan = location_get_source_line (tmp.get_filename (), 1);
+  ASSERT_EQ (line_display_cols,
+            cpp_display_width (lspan.get_buffer (), lspan.length ()));
+  ASSERT_EQ (line_display_cols,
+            location_compute_display_column (expand_location (line_end)));
+  ASSERT_EQ (0, memcmp (lspan.get_buffer () + (emoji_col - 1),
+                       "\xf0\x9f\x98\x82\xf0\x9f\x98\x82", 8));
+
+  /* (caret_byte, max_width, expected_x_offset_display, [left_margin])  */
+
+  /* No constraint on the width -> no offset.  */
+  test_offset_impl (emoji_col, 0, 0);
+
+  /* Caret is before the beginning -> no offset.  */
+  test_offset_impl (0, 100, 0);
+
+  /* Caret is past the end of the line -> no offset.  */
+  test_offset_impl (line_bytes+1, 100, 0);
+
+  /* Line fits in the display -> no offset.  */
+  test_offset_impl (line_bytes, line_display_cols + test_left_margin, 0);
+  test_offset_impl (emoji_col, line_display_cols + test_left_margin, 0);
+
+  /* Line is too long for the display but caret location is OK
+     anyway -> no offset.  */
+  static const int small_width = 24;
+  test_offset_impl (1, small_width, 0);
+
+  /* Width constraint is very small -> no offset.  */
+  test_offset_impl (emoji_col, CARET_LINE_MARGIN, 0);
+
+  /* Line would be offset, but due to large line numbers, offsetting
+     would remove the whole line -> no offset.  */
+  static const int huge_left_margin = 100;
+  test_offset_impl (emoji_col, huge_left_margin, 0, huge_left_margin);
+
+  /* Line is the same length as the display, but the line number makes it too
+     long, so offset is required.  Caret is at the end so padding on the right
+     is not in effect.  */
+  for (int excess = 1; excess <= 3; ++excess)
+    test_offset_impl (line_bytes, line_display_cols + test_left_margin - excess,
+                     excess);
+
+  /* Line is much too long for the display, caret is near the end ->
+     offset should be such that the line fits in the display and caret
+     remains the same distance from the end that it was.  */
+  for (int caret_offset = 0, max_offset = MIN (CARET_LINE_MARGIN, 10);
+       caret_offset <= max_offset; ++caret_offset)
+    test_offset_impl (line_bytes - caret_offset, small_width,
+                     line_display_cols + test_left_margin - small_width);
+
+  /* As previous case but caret is closer to the middle; now we want it to end
+     up CARET_LINE_MARGIN bytes from the end.  */
+  ASSERT_GT (line_display_cols - emoji_col, CARET_LINE_MARGIN);
+  test_offset_impl (emoji_col, small_width,
+                   emoji_col + test_left_margin
+                   - (small_width - CARET_LINE_MARGIN));
+
+  /* Test that the source line is offset as expected when printed.  */
   {
-    diagnostic_finish (this);
+    test_diagnostic_context dc;
+    dc.caret_max_width = small_width - 6;
+    dc.min_margin_width = test_left_margin - test_linenum_sep + 1;
+    dc.show_line_numbers_p = true;
+    dc.show_ruler_p = true;
+    rich_location richloc (line_table,
+                          linemap_position_for_column (line_table,
+                                                       emoji_col));
+    layout test_layout (&dc, &richloc, DK_ERROR);
+    test_layout.print_line (1);
+    ASSERT_STREQ ("     |         1         \n"
+                 "     |         1         \n"
+                 "     | 234567890123456789\n"
+                 "   1 | \xf0\x9f\x98\x82\xf0\x9f\x98\x82 is a pair of emojis "
+                 "that occupies 8 bytes and 4 display columns, starting at "
+                 "column #102.\n"
+                 "     | ^\n\n",
+                 pp_formatted_text (dc.printer));
   }
 
-  /* Implementation of diagnostic_start_span_fn, hiding the
-     real filename (to avoid printing the names of tempfiles).  */
-  static void
-  start_span_cb (diagnostic_context *context, expanded_location exploc)
+  /* Similar to the previous example, but now the offset called for would split
+     the first emoji in the middle of the UTF-8 sequence.  Check that we replace
+     it with a padding space in this case.  */
   {
-    exploc.file = "FILENAME";
-    default_diagnostic_start_span_fn (context, exploc);
+    test_diagnostic_context dc;
+    dc.caret_max_width = small_width - 5;
+    dc.min_margin_width = test_left_margin - test_linenum_sep + 1;
+    dc.show_line_numbers_p = true;
+    dc.show_ruler_p = true;
+    rich_location richloc (line_table,
+                          linemap_position_for_column (line_table,
+                                                       emoji_col + 2));
+    layout test_layout (&dc, &richloc, DK_ERROR);
+    test_layout.print_line (1);
+    ASSERT_STREQ ("     |        1         1 \n"
+                 "     |        1         2 \n"
+                 "     | 3456789012345678901\n"
+                 "   1 |  \xf0\x9f\x98\x82 is a pair of emojis "
+                 "that occupies 8 bytes and 4 display columns, starting at "
+                 "column #102.\n"
+                 "     |  ^\n\n",
+                 pp_formatted_text (dc.printer));
   }
-};
+
+}
 
 /* Verify that diagnostic_show_locus works sanely on UNKNOWN_LOCATION.  */
 
@@ -1869,7 +2782,7 @@ test_diagnostic_show_locus_unknown_location ()
   test_diagnostic_context dc;
   rich_location richloc (line_table, UNKNOWN_LOCATION);
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n", pp_formatted_text (dc.printer));
+  ASSERT_STREQ ("", pp_formatted_text (dc.printer));
 }
 
 /* Verify that diagnostic_show_locus works sanely for various
@@ -1891,8 +2804,7 @@ test_one_liner_simple_caret ()
   location_t caret = linemap_position_for_column (line_table, 10);
   rich_location richloc (line_table, caret);
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-               " foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
                "          ^\n",
                pp_formatted_text (dc.printer));
 }
@@ -1909,8 +2821,7 @@ test_one_liner_caret_and_range ()
   location_t loc = make_location (caret, start, finish);
   rich_location richloc (line_table, loc);
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-               " foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
                "       ~~~^~~~~~\n",
                pp_formatted_text (dc.printer));
 }
@@ -1940,11 +2851,10 @@ test_one_liner_multiple_carets_and_ranges ()
   dc.caret_chars[2] = 'C';
 
   rich_location richloc (line_table, foo);
-  richloc.add_range (bar, true);
-  richloc.add_range (field, true);
+  richloc.add_range (bar, SHOW_RANGE_WITH_CARET);
+  richloc.add_range (field, SHOW_RANGE_WITH_CARET);
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-               " foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
                " ~A~   ~B~ ~~C~~\n",
                pp_formatted_text (dc.printer));
 }
@@ -1959,8 +2869,7 @@ test_one_liner_fixit_insert_before ()
   rich_location richloc (line_table, caret);
   richloc.add_fixit_insert_before ("&");
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-               " foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
                "       ^\n"
                "       &\n",
                pp_formatted_text (dc.printer));
@@ -1978,30 +2887,94 @@ test_one_liner_fixit_insert_after ()
   rich_location richloc (line_table, foo);
   richloc.add_fixit_insert_after ("[0]");
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-               " foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
                " ^~~\n"
                "    [0]\n",
                pp_formatted_text (dc.printer));
 }
 
-/* Removal fix-it hint: removal of the ".field". */
+/* Removal fix-it hint: removal of the ".field".
+   Also verify the interaction of pp_set_prefix with rulers and
+   fix-it hints.  */
 
 static void
 test_one_liner_fixit_remove ()
 {
-  test_diagnostic_context dc;
   location_t start = linemap_position_for_column (line_table, 10);
   location_t finish = linemap_position_for_column (line_table, 15);
   location_t dot = make_location (start, start, finish);
   rich_location richloc (line_table, dot);
   richloc.add_fixit_remove ();
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-               " foo = bar.field;\n"
-               "          ^~~~~~\n"
-               "          ------\n",
-               pp_formatted_text (dc.printer));
+
+  /* Normal.  */
+  {
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ (" foo = bar.field;\n"
+                 "          ^~~~~~\n"
+                 "          ------\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Test of adding a prefix.  */
+  {
+    test_diagnostic_context dc;
+    pp_prefixing_rule (dc.printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+    pp_set_prefix (dc.printer, xstrdup ("TEST PREFIX:"));
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("TEST PREFIX: foo = bar.field;\n"
+                 "TEST PREFIX:          ^~~~~~\n"
+                 "TEST PREFIX:          ------\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Normal, with ruler.  */
+  {
+    test_diagnostic_context dc;
+    dc.show_ruler_p = true;
+    dc.caret_max_width = 104;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("          0         0         0         0         0         0         0         0         0         1    \n"
+                 "          1         2         3         4         5         6         7         8         9         0    \n"
+                 " 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234\n"
+                 " foo = bar.field;\n"
+                 "          ^~~~~~\n"
+                 "          ------\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Test of adding a prefix, with ruler.  */
+  {
+    test_diagnostic_context dc;
+    dc.show_ruler_p = true;
+    dc.caret_max_width = 50;
+    pp_prefixing_rule (dc.printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+    pp_set_prefix (dc.printer, xstrdup ("TEST PREFIX:"));
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("TEST PREFIX:          1         2         3         4         5\n"
+                 "TEST PREFIX: 12345678901234567890123456789012345678901234567890\n"
+                 "TEST PREFIX: foo = bar.field;\n"
+                 "TEST PREFIX:          ^~~~~~\n"
+                 "TEST PREFIX:          ------\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Test of adding a prefix, with ruler and line numbers.  */
+  {
+    test_diagnostic_context dc;
+    dc.show_ruler_p = true;
+    dc.caret_max_width = 50;
+    dc.show_line_numbers_p = true;
+    pp_prefixing_rule (dc.printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+    pp_set_prefix (dc.printer, xstrdup ("TEST PREFIX:"));
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("TEST PREFIX:      |          1         2         3         4         5\n"
+                 "TEST PREFIX:      | 12345678901234567890123456789012345678901234567890\n"
+                 "TEST PREFIX:    1 | foo = bar.field;\n"
+                 "TEST PREFIX:      |          ^~~~~~\n"
+                 "TEST PREFIX:      |          ------\n",
+                 pp_formatted_text (dc.printer));
+  }
 }
 
 /* Replace fix-it hint: replacing "field" with "m_field". */
@@ -2016,8 +2989,7 @@ test_one_liner_fixit_replace ()
   rich_location richloc (line_table, field);
   richloc.add_fixit_replace ("m_field");
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-               " foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
                "           ^~~~~\n"
                "           m_field\n",
                pp_formatted_text (dc.printer));
@@ -2041,8 +3013,7 @@ test_one_liner_fixit_replace_non_equal_range ()
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
   /* The replacement range is not indicated in the annotation line, so
      it should be indicated via an additional underline.  */
-  ASSERT_STREQ ("\n"
-               " foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
                "     ^\n"
                "           -----\n"
                "           m_field\n",
@@ -2062,13 +3033,12 @@ test_one_liner_fixit_replace_equal_secondary_range ()
   location_t finish = linemap_position_for_column (line_table, 15);
   rich_location richloc (line_table, equals);
   location_t field = make_location (start, start, finish);
-  richloc.add_range (field, false);
+  richloc.add_range (field);
   richloc.add_fixit_replace (field, "m_field");
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
   /* The replacement range is indicated in the annotation line,
      so it shouldn't be indicated via an additional underline.  */
-  ASSERT_STREQ ("\n"
-               " foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
                "     ^     ~~~~~\n"
                "           m_field\n",
                pp_formatted_text (dc.printer));
@@ -2101,8 +3071,7 @@ test_one_liner_fixit_validation_adhoc_locations ()
 
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-                 " foo = bar.field;\n"
+    ASSERT_STREQ (" foo = bar.field;\n"
                  "       ^~~~~~~~~~                               \n"
                  "       test\n",
                  pp_formatted_text (dc.printer));
@@ -2118,8 +3087,7 @@ test_one_liner_fixit_validation_adhoc_locations ()
 
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-                 " foo = bar.field;\n"
+    ASSERT_STREQ (" foo = bar.field;\n"
                  "       ^~~~~~~~~~                               \n"
                  "       -----------------------------------------\n",
                  pp_formatted_text (dc.printer));
@@ -2135,8 +3103,7 @@ test_one_liner_fixit_validation_adhoc_locations ()
 
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-                 " foo = bar.field;\n"
+    ASSERT_STREQ (" foo = bar.field;\n"
                  "       ^~~~~~~~~~                               \n"
                  "       test\n",
                  pp_formatted_text (dc.printer));
@@ -2155,8 +3122,7 @@ test_one_liner_many_fixits_1 ()
     richloc.add_fixit_insert_before ("a");
   ASSERT_EQ (1, richloc.get_num_fixit_hints ());
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-               " foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
                "     ^\n"
                "     aaaaaaaaaaaaaaaaaaa\n",
                pp_formatted_text (dc.printer));
@@ -2178,13 +3144,197 @@ test_one_liner_many_fixits_2 ()
     }
   ASSERT_EQ (19, richloc.get_num_fixit_hints ());
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-               " foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
                "     ^\n"
                "a a a a a a a a a a a a a a a a a a a\n",
                pp_formatted_text (dc.printer));
 }
 
+/* Test of labeling the ranges within a rich_location.  */
+
+static void
+test_one_liner_labels ()
+{
+  location_t foo
+    = make_location (linemap_position_for_column (line_table, 1),
+                    linemap_position_for_column (line_table, 1),
+                    linemap_position_for_column (line_table, 3));
+  location_t bar
+    = make_location (linemap_position_for_column (line_table, 7),
+                    linemap_position_for_column (line_table, 7),
+                    linemap_position_for_column (line_table, 9));
+  location_t field
+    = make_location (linemap_position_for_column (line_table, 11),
+                    linemap_position_for_column (line_table, 11),
+                    linemap_position_for_column (line_table, 15));
+
+  /* Example where all the labels fit on one line.  */
+  {
+    text_range_label label0 ("0");
+    text_range_label label1 ("1");
+    text_range_label label2 ("2");
+    gcc_rich_location richloc (foo, &label0);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+    richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+    {
+      test_diagnostic_context dc;
+      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      ASSERT_STREQ (" foo = bar.field;\n"
+                   " ^~~   ~~~ ~~~~~\n"
+                   " |     |   |\n"
+                   " 0     1   2\n",
+                   pp_formatted_text (dc.printer));
+    }
+
+    /* Verify that we can disable label-printing.  */
+    {
+      test_diagnostic_context dc;
+      dc.show_labels_p = false;
+      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      ASSERT_STREQ (" foo = bar.field;\n"
+                   " ^~~   ~~~ ~~~~~\n",
+                   pp_formatted_text (dc.printer));
+    }
+  }
+
+  /* Example where the labels need extra lines.  */
+  {
+    text_range_label label0 ("label 0");
+    text_range_label label1 ("label 1");
+    text_range_label label2 ("label 2");
+    gcc_rich_location richloc (foo, &label0);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+    richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ (" foo = bar.field;\n"
+                 " ^~~   ~~~ ~~~~~\n"
+                 " |     |   |\n"
+                 " |     |   label 2\n"
+                 " |     label 1\n"
+                 " label 0\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Example of boundary conditions: label 0 and 1 have just enough clearance,
+     but label 1 just touches label 2.  */
+  {
+    text_range_label label0 ("aaaaa");
+    text_range_label label1 ("bbbb");
+    text_range_label label2 ("c");
+    gcc_rich_location richloc (foo, &label0);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+    richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ (" foo = bar.field;\n"
+                 " ^~~   ~~~ ~~~~~\n"
+                 " |     |   |\n"
+                 " |     |   c\n"
+                 " aaaaa bbbb\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Example of out-of-order ranges (thus requiring a sort).  */
+  {
+    text_range_label label0 ("0");
+    text_range_label label1 ("1");
+    text_range_label label2 ("2");
+    gcc_rich_location richloc (field, &label0);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+    richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ (" foo = bar.field;\n"
+                 " ~~~   ~~~ ^~~~~\n"
+                 " |     |   |\n"
+                 " 2     1   0\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Ensure we don't ICE if multiple ranges with labels are on
+     the same point.  */
+  {
+    text_range_label label0 ("label 0");
+    text_range_label label1 ("label 1");
+    text_range_label label2 ("label 2");
+    gcc_rich_location richloc (bar, &label0);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ (" foo = bar.field;\n"
+                 "       ^~~\n"
+                 "       |\n"
+                 "       label 0\n"
+                 "       label 1\n"
+                 "       label 2\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Example of out-of-order ranges (thus requiring a sort), where
+     they overlap, and there are multiple ranges on the same point.  */
+  {
+    text_range_label label_0a ("label 0a");
+    text_range_label label_1a ("label 1a");
+    text_range_label label_2a ("label 2a");
+    text_range_label label_0b ("label 0b");
+    text_range_label label_1b ("label 1b");
+    text_range_label label_2b ("label 2b");
+    text_range_label label_0c ("label 0c");
+    text_range_label label_1c ("label 1c");
+    text_range_label label_2c ("label 2c");
+    gcc_rich_location richloc (field, &label_0a);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label_1a);
+    richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label_2a);
+
+    richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label_0b);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label_1b);
+    richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label_2b);
+
+    richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label_0c);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label_1c);
+    richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label_2c);
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ (" foo = bar.field;\n"
+                 " ~~~   ~~~ ^~~~~\n"
+                 " |     |   |\n"
+                 " |     |   label 0a\n"
+                 " |     |   label 0b\n"
+                 " |     |   label 0c\n"
+                 " |     label 1a\n"
+                 " |     label 1b\n"
+                 " |     label 1c\n"
+                 " label 2a\n"
+                 " label 2b\n"
+                 " label 2c\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Verify that a NULL result from range_label::get_text is
+     handled gracefully.  */
+  {
+    text_range_label label (NULL);
+    gcc_rich_location richloc (bar, &label);
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ (" foo = bar.field;\n"
+                 "       ^~~\n",
+                 pp_formatted_text (dc.printer));
+   }
+
+  /* TODO: example of formatted printing (needs to be in
+     gcc-rich-location.c due to Makefile.in issues).  */
+}
+
 /* Run the various one-liner tests.  */
 
 static void
@@ -2221,6 +3371,598 @@ test_diagnostic_show_locus_one_liner (const line_table_case &case_)
   test_one_liner_fixit_validation_adhoc_locations ();
   test_one_liner_many_fixits_1 ();
   test_one_liner_many_fixits_2 ();
+  test_one_liner_labels ();
+}
+
+/* Version of all one-liner tests exercising multibyte awareness.  For
+   simplicity we stick to using two multibyte characters in the test, U+1F602
+   == "\xf0\x9f\x98\x82", which uses 4 bytes and 2 display columns, and U+03C0
+   == "\xcf\x80", which uses 2 bytes and 1 display column.  Note: all of the
+   below asserts would be easier to read if we used UTF-8 directly in the
+   string constants, but it seems better not to demand the host compiler
+   support this, when it isn't otherwise necessary.  Instead, whenever an
+   extended character appears in a string, we put a line break after it so that
+   all succeeding characters can appear visually at the correct display column.
+
+   All of these work on the following 1-line source file:
+
+     .0000000001111111111222222   display
+     .1234567890123456789012345   columns
+     "SS_foo = P_bar.SS_fieldP;\n"
+     .0000000111111111222222223   byte
+     .1356789012456789134567891   columns
+
+   which is set up by test_diagnostic_show_locus_one_liner and calls
+   them.  Here SS represents the two display columns for the U+1F602 emoji and
+   P represents the one display column for the U+03C0 pi symbol.  */
+
+/* Just a caret.  */
+
+static void
+test_one_liner_simple_caret_utf8 ()
+{
+  test_diagnostic_context dc;
+  location_t caret = linemap_position_for_column (line_table, 18);
+  rich_location richloc (line_table, caret);
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                  "_foo = \xcf\x80"
+                          "_bar.\xf0\x9f\x98\x82"
+                                 "_field\xcf\x80"
+                                        ";\n"
+               "               ^\n",
+               pp_formatted_text (dc.printer));
+}
+
+/* Caret and range.  */
+static void
+test_one_liner_caret_and_range_utf8 ()
+{
+  test_diagnostic_context dc;
+  location_t caret = linemap_position_for_column (line_table, 18);
+  location_t start = linemap_position_for_column (line_table, 12);
+  location_t finish = linemap_position_for_column (line_table, 30);
+  location_t loc = make_location (caret, start, finish);
+  rich_location richloc (line_table, loc);
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                  "_foo = \xcf\x80"
+                          "_bar.\xf0\x9f\x98\x82"
+                                 "_field\xcf\x80"
+                                        ";\n"
+               "          ~~~~~^~~~~~~~~~\n",
+               pp_formatted_text (dc.printer));
+}
+
+/* Multiple ranges and carets.  */
+
+static void
+test_one_liner_multiple_carets_and_ranges_utf8 ()
+{
+  test_diagnostic_context dc;
+  location_t foo
+    = 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';
+
+  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';
+
+  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';
+  rich_location richloc (line_table, foo);
+  richloc.add_range (bar, SHOW_RANGE_WITH_CARET);
+  richloc.add_range (field, SHOW_RANGE_WITH_CARET);
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                  "_foo = \xcf\x80"
+                          "_bar.\xf0\x9f\x98\x82"
+                                 "_field\xcf\x80"
+                                        ";\n"
+               " ~~~~A~   ~~~B~ ~~~~~C~~~\n",
+               pp_formatted_text (dc.printer));
+}
+
+/* Insertion fix-it hint: adding an "&" to the front of "P_bar.field". */
+
+static void
+test_one_liner_fixit_insert_before_utf8 ()
+{
+  test_diagnostic_context dc;
+  location_t caret = linemap_position_for_column (line_table, 12);
+  rich_location richloc (line_table, caret);
+  richloc.add_fixit_insert_before ("&");
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                  "_foo = \xcf\x80"
+                          "_bar.\xf0\x9f\x98\x82"
+                                 "_field\xcf\x80"
+                                        ";\n"
+               "          ^\n"
+               "          &\n",
+               pp_formatted_text (dc.printer));
+}
+
+/* Insertion fix-it hint: adding a "[0]" after "SS_foo". */
+
+static void
+test_one_liner_fixit_insert_after_utf8 ()
+{
+  test_diagnostic_context dc;
+  location_t start = linemap_position_for_column (line_table, 1);
+  location_t finish = linemap_position_for_column (line_table, 8);
+  location_t foo = make_location (start, start, finish);
+  rich_location richloc (line_table, foo);
+  richloc.add_fixit_insert_after ("[0]");
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                  "_foo = \xcf\x80"
+                          "_bar.\xf0\x9f\x98\x82"
+                                 "_field\xcf\x80"
+                                        ";\n"
+               " ^~~~~~\n"
+               "       [0]\n",
+               pp_formatted_text (dc.printer));
+}
+
+/* Removal fix-it hint: removal of the ".SS_fieldP". */
+
+static void
+test_one_liner_fixit_remove_utf8 ()
+{
+  test_diagnostic_context dc;
+  location_t start = linemap_position_for_column (line_table, 18);
+  location_t finish = linemap_position_for_column (line_table, 30);
+  location_t dot = make_location (start, start, finish);
+  rich_location richloc (line_table, dot);
+  richloc.add_fixit_remove ();
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                  "_foo = \xcf\x80"
+                          "_bar.\xf0\x9f\x98\x82"
+                                 "_field\xcf\x80"
+                                        ";\n"
+               "               ^~~~~~~~~~\n"
+               "               ----------\n",
+               pp_formatted_text (dc.printer));
+}
+
+/* Replace fix-it hint: replacing "SS_fieldP" with "m_SSfieldP". */
+
+static void
+test_one_liner_fixit_replace_utf8 ()
+{
+  test_diagnostic_context dc;
+  location_t start = linemap_position_for_column (line_table, 19);
+  location_t finish = linemap_position_for_column (line_table, 30);
+  location_t field = make_location (start, start, finish);
+  rich_location richloc (line_table, field);
+  richloc.add_fixit_replace ("m_\xf0\x9f\x98\x82_field\xcf\x80");
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                  "_foo = \xcf\x80"
+                          "_bar.\xf0\x9f\x98\x82"
+                                 "_field\xcf\x80"
+                                        ";\n"
+               "                ^~~~~~~~~\n"
+               "                m_\xf0\x9f\x98\x82"
+                                   "_field\xcf\x80\n",
+               pp_formatted_text (dc.printer));
+}
+
+/* Replace fix-it hint: replacing "SS_fieldP" with "m_SSfieldP",
+   but where the caret was elsewhere.  */
+
+static void
+test_one_liner_fixit_replace_non_equal_range_utf8 ()
+{
+  test_diagnostic_context dc;
+  location_t equals = linemap_position_for_column (line_table, 10);
+  location_t start = linemap_position_for_column (line_table, 19);
+  location_t finish = linemap_position_for_column (line_table, 30);
+  rich_location richloc (line_table, equals);
+  source_range range;
+  range.m_start = start;
+  range.m_finish = finish;
+  richloc.add_fixit_replace (range, "m_\xf0\x9f\x98\x82_field\xcf\x80");
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  /* The replacement range is not indicated in the annotation line, so
+     it should be indicated via an additional underline.  */
+  ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                  "_foo = \xcf\x80"
+                          "_bar.\xf0\x9f\x98\x82"
+                                 "_field\xcf\x80"
+                                        ";\n"
+               "        ^\n"
+               "                ---------\n"
+               "                m_\xf0\x9f\x98\x82"
+                                   "_field\xcf\x80\n",
+               pp_formatted_text (dc.printer));
+}
+
+/* Replace fix-it hint: replacing "SS_fieldP" with "m_SSfieldP",
+   where the caret was elsewhere, but where a secondary range
+   exactly covers "field".  */
+
+static void
+test_one_liner_fixit_replace_equal_secondary_range_utf8 ()
+{
+  test_diagnostic_context dc;
+  location_t equals = linemap_position_for_column (line_table, 10);
+  location_t start = linemap_position_for_column (line_table, 19);
+  location_t finish = linemap_position_for_column (line_table, 30);
+  rich_location richloc (line_table, equals);
+  location_t field = make_location (start, start, finish);
+  richloc.add_range (field);
+  richloc.add_fixit_replace (field, "m_\xf0\x9f\x98\x82_field\xcf\x80");
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  /* The replacement range is indicated in the annotation line,
+     so it shouldn't be indicated via an additional underline.  */
+  ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                  "_foo = \xcf\x80"
+                          "_bar.\xf0\x9f\x98\x82"
+                                 "_field\xcf\x80"
+                                        ";\n"
+               "        ^       ~~~~~~~~~\n"
+               "                m_\xf0\x9f\x98\x82"
+                                   "_field\xcf\x80\n",
+               pp_formatted_text (dc.printer));
+}
+
+/* Verify that we can use ad-hoc locations when adding fixits to a
+   rich_location.  */
+
+static void
+test_one_liner_fixit_validation_adhoc_locations_utf8 ()
+{
+  /* Generate a range that's too long to be packed, so must
+     be stored as an ad-hoc location (given the defaults
+     of 5 bits or 0 bits of packed range); 41 columns > 2**5.  */
+  const location_t c12 = linemap_position_for_column (line_table, 12);
+  const location_t c52 = linemap_position_for_column (line_table, 52);
+  const location_t loc = make_location (c12, c12, c52);
+
+  if (c52 > LINE_MAP_MAX_LOCATION_WITH_COLS)
+    return;
+
+  ASSERT_TRUE (IS_ADHOC_LOC (loc));
+
+  /* Insert.  */
+  {
+    rich_location richloc (line_table, loc);
+    richloc.add_fixit_insert_before (loc, "test");
+    /* It should not have been discarded by the validator.  */
+    ASSERT_EQ (1, richloc.get_num_fixit_hints ());
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                    "_foo = \xcf\x80"
+                            "_bar.\xf0\x9f\x98\x82"
+                                   "_field\xcf\x80"
+                                          ";\n"
+                 "          ^~~~~~~~~~~~~~~~                     \n"
+                 "          test\n",
+               pp_formatted_text (dc.printer));
+  }
+
+  /* Remove.  */
+  {
+    rich_location richloc (line_table, loc);
+    source_range range = source_range::from_locations (loc, c52);
+    richloc.add_fixit_remove (range);
+    /* It should not have been discarded by the validator.  */
+    ASSERT_EQ (1, richloc.get_num_fixit_hints ());
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                    "_foo = \xcf\x80"
+                            "_bar.\xf0\x9f\x98\x82"
+                                   "_field\xcf\x80"
+                                          ";\n"
+                 "          ^~~~~~~~~~~~~~~~                     \n"
+                 "          -------------------------------------\n",
+               pp_formatted_text (dc.printer));
+  }
+
+  /* Replace.  */
+  {
+    rich_location richloc (line_table, loc);
+    source_range range = source_range::from_locations (loc, c52);
+    richloc.add_fixit_replace (range, "test");
+    /* It should not have been discarded by the validator.  */
+    ASSERT_EQ (1, richloc.get_num_fixit_hints ());
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                    "_foo = \xcf\x80"
+                            "_bar.\xf0\x9f\x98\x82"
+                                   "_field\xcf\x80"
+                                          ";\n"
+                 "          ^~~~~~~~~~~~~~~~                     \n"
+                 "          test\n",
+               pp_formatted_text (dc.printer));
+  }
+}
+
+/* Test of consolidating insertions at the same location.  */
+
+static void
+test_one_liner_many_fixits_1_utf8 ()
+{
+  test_diagnostic_context dc;
+  location_t equals = linemap_position_for_column (line_table, 10);
+  rich_location richloc (line_table, equals);
+  for (int i = 0; i < 19; i++)
+    richloc.add_fixit_insert_before (i & 1 ? "@" : "\xcf\x80");
+  ASSERT_EQ (1, richloc.get_num_fixit_hints ());
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                  "_foo = \xcf\x80"
+                          "_bar.\xf0\x9f\x98\x82"
+                                 "_field\xcf\x80"
+                                        ";\n"
+               "        ^\n"
+               "        \xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80@"
+               "\xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80\n",
+               pp_formatted_text (dc.printer));
+}
+
+/* Ensure that we can add an arbitrary number of fix-it hints to a
+   rich_location, even if they are not consolidated.  */
+
+static void
+test_one_liner_many_fixits_2_utf8 ()
+{
+  test_diagnostic_context dc;
+  location_t equals = linemap_position_for_column (line_table, 10);
+  rich_location richloc (line_table, equals);
+  const int nlocs = 19;
+  int locs[nlocs] = {1, 5, 7, 9, 11, 14, 16, 18, 23, 25, 27, 29, 32,
+                    34, 36, 38, 40, 42, 44};
+  for (int i = 0; i != nlocs; ++i)
+    {
+      location_t loc = linemap_position_for_column (line_table, locs[i]);
+      richloc.add_fixit_insert_before (loc, i & 1 ? "@" : "\xcf\x80");
+    }
+
+  ASSERT_EQ (nlocs, richloc.get_num_fixit_hints ());
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                  "_foo = \xcf\x80"
+                          "_bar.\xf0\x9f\x98\x82"
+                                 "_field\xcf\x80"
+                                        ";\n"
+               "        ^\n"
+               " \xcf\x80 @ \xcf\x80 @ \xcf\x80 @ \xcf\x80 @  \xcf\x80 @"
+               " \xcf\x80 @ \xcf\x80 @ \xcf\x80 @ \xcf\x80 @ \xcf\x80\n",
+               pp_formatted_text (dc.printer));
+}
+
+/* Test of labeling the ranges within a rich_location.  */
+
+static void
+test_one_liner_labels_utf8 ()
+{
+  location_t foo
+    = make_location (linemap_position_for_column (line_table, 1),
+                    linemap_position_for_column (line_table, 1),
+                    linemap_position_for_column (line_table, 8));
+  location_t bar
+    = make_location (linemap_position_for_column (line_table, 12),
+                    linemap_position_for_column (line_table, 12),
+                    linemap_position_for_column (line_table, 17));
+  location_t field
+    = make_location (linemap_position_for_column (line_table, 19),
+                    linemap_position_for_column (line_table, 19),
+                    linemap_position_for_column (line_table, 30));
+
+  /* Example where all the labels fit on one line.  */
+  {
+    /* These three labels contain multibyte characters such that their byte
+       lengths are respectively (12, 10, 18), but their display widths are only
+       (6, 5, 9).  All three fit on the line when considering the display
+       widths, but not when considering the byte widths, so verify that we do
+       indeed put them all on one line.  */
+    text_range_label label0
+      ("\xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80");
+    text_range_label label1
+      ("\xf0\x9f\x98\x82\xf0\x9f\x98\x82\xcf\x80");
+    text_range_label label2
+      ("\xf0\x9f\x98\x82\xcf\x80\xf0\x9f\x98\x82\xf0\x9f\x98\x82\xcf\x80"
+       "\xcf\x80");
+    gcc_rich_location richloc (foo, &label0);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+    richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+    {
+      test_diagnostic_context dc;
+      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                      "_foo = \xcf\x80"
+                              "_bar.\xf0\x9f\x98\x82"
+                                     "_field\xcf\x80"
+                                            ";\n"
+                   " ^~~~~~   ~~~~~ ~~~~~~~~~\n"
+                   " |        |     |\n"
+                   " \xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80"
+                          "   \xf0\x9f\x98\x82\xf0\x9f\x98\x82\xcf\x80"
+                                  " \xf0\x9f\x98\x82\xcf\x80\xf0\x9f\x98\x82"
+                                        "\xf0\x9f\x98\x82\xcf\x80\xcf\x80\n",
+                   pp_formatted_text (dc.printer));
+    }
+
+  }
+
+  /* Example where the labels need extra lines.  */
+  {
+    text_range_label label0 ("label 0\xf0\x9f\x98\x82");
+    text_range_label label1 ("label 1\xcf\x80");
+    text_range_label label2 ("label 2\xcf\x80");
+    gcc_rich_location richloc (foo, &label0);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+    richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+
+    ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                    "_foo = \xcf\x80"
+                            "_bar.\xf0\x9f\x98\x82"
+                                   "_field\xcf\x80"
+                                          ";\n"
+                 " ^~~~~~   ~~~~~ ~~~~~~~~~\n"
+                 " |        |     |\n"
+                 " |        |     label 2\xcf\x80\n"
+                 " |        label 1\xcf\x80\n"
+                 " label 0\xf0\x9f\x98\x82\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Example of boundary conditions: label 0 and 1 have just enough clearance,
+     but label 1 just touches label 2.  */
+  {
+    text_range_label label0 ("aaaaa\xf0\x9f\x98\x82\xcf\x80");
+    text_range_label label1 ("bb\xf0\x9f\x98\x82\xf0\x9f\x98\x82");
+    text_range_label label2 ("c");
+    gcc_rich_location richloc (foo, &label0);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+    richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ (" \xf0\x9f\x98\x82"
+                    "_foo = \xcf\x80"
+                            "_bar.\xf0\x9f\x98\x82"
+                                   "_field\xcf\x80"
+                                          ";\n"
+                 " ^~~~~~   ~~~~~ ~~~~~~~~~\n"
+                 " |        |     |\n"
+                 " |        |     c\n"
+                 " aaaaa\xf0\x9f\x98\x82\xcf\x80"
+                          " bb\xf0\x9f\x98\x82\xf0\x9f\x98\x82\n",
+                 pp_formatted_text (dc.printer));
+  }
+}
+
+/* Run the various one-liner tests.  */
+
+static void
+test_diagnostic_show_locus_one_liner_utf8 (const line_table_case &case_)
+{
+  /* Create a tempfile and write some text to it.  */
+  const char *content
+    /* Display columns.
+       0000000000000000000000011111111111111111111111111111112222222222222
+       1111111122222222345678900000000123456666666677777777890123444444445  */
+    = "\xf0\x9f\x98\x82_foo = \xcf\x80_bar.\xf0\x9f\x98\x82_field\xcf\x80;\n";
+    /* 0000000000000000000001111111111111111111222222222222222222222233333
+       1111222233334444567890122223333456789999000011112222345678999900001
+       Byte columns.  */
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  line_table_test ltt (case_);
+
+  linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1);
+
+  location_t line_end = linemap_position_for_column (line_table, 31);
+
+  /* Don't attempt to run the tests if column data might be unavailable.  */
+  if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
+    return;
+
+  ASSERT_STREQ (tmp.get_filename (), LOCATION_FILE (line_end));
+  ASSERT_EQ (1, LOCATION_LINE (line_end));
+  ASSERT_EQ (31, LOCATION_COLUMN (line_end));
+
+  char_span lspan = location_get_source_line (tmp.get_filename (), 1);
+  ASSERT_EQ (25, cpp_display_width (lspan.get_buffer (), lspan.length ()));
+  ASSERT_EQ (25, location_compute_display_column (expand_location (line_end)));
+
+  test_one_liner_simple_caret_utf8 ();
+  test_one_liner_caret_and_range_utf8 ();
+  test_one_liner_multiple_carets_and_ranges_utf8 ();
+  test_one_liner_fixit_insert_before_utf8 ();
+  test_one_liner_fixit_insert_after_utf8 ();
+  test_one_liner_fixit_remove_utf8 ();
+  test_one_liner_fixit_replace_utf8 ();
+  test_one_liner_fixit_replace_non_equal_range_utf8 ();
+  test_one_liner_fixit_replace_equal_secondary_range_utf8 ();
+  test_one_liner_fixit_validation_adhoc_locations_utf8 ();
+  test_one_liner_many_fixits_1_utf8 ();
+  test_one_liner_many_fixits_2_utf8 ();
+  test_one_liner_labels_utf8 ();
+}
+
+/* Verify that gcc_rich_location::add_location_if_nearby works.  */
+
+static void
+test_add_location_if_nearby (const line_table_case &case_)
+{
+  /* Create a tempfile and write some text to it.
+     ...000000000111111111122222222223333333333.
+     ...123456789012345678901234567890123456789.  */
+  const char *content
+    = ("struct same_line { double x; double y; ;\n" /* line 1.  */
+       "struct different_line\n"                    /* line 2.  */
+       "{\n"                                        /* line 3.  */
+       "  double x;\n"                              /* line 4.  */
+       "  double y;\n"                              /* line 5.  */
+       ";\n");                                      /* line 6.  */
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  line_table_test ltt (case_);
+
+  const line_map_ordinary *ord_map
+    = linemap_check_ordinary (linemap_add (line_table, LC_ENTER, false,
+                                          tmp.get_filename (), 0));
+
+  linemap_line_start (line_table, 1, 100);
+
+  const location_t final_line_end
+    = linemap_position_for_line_and_column (line_table, ord_map, 6, 7);
+
+  /* Don't attempt to run the tests if column data might be unavailable.  */
+  if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
+    return;
+
+  /* Test of add_location_if_nearby on the same line as the
+     primary location.  */
+  {
+    const location_t missing_close_brace_1_39
+      = linemap_position_for_line_and_column (line_table, ord_map, 1, 39);
+    const location_t matching_open_brace_1_18
+      = linemap_position_for_line_and_column (line_table, ord_map, 1, 18);
+    gcc_rich_location richloc (missing_close_brace_1_39);
+    bool added = richloc.add_location_if_nearby (matching_open_brace_1_18);
+    ASSERT_TRUE (added);
+    ASSERT_EQ (2, richloc.get_num_locations ());
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ (" struct same_line { double x; double y; ;\n"
+                 "                  ~                    ^\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Test of add_location_if_nearby on a different line to the
+     primary location.  */
+  {
+    const location_t missing_close_brace_6_1
+      = linemap_position_for_line_and_column (line_table, ord_map, 6, 1);
+    const location_t matching_open_brace_3_1
+      = linemap_position_for_line_and_column (line_table, ord_map, 3, 1);
+    gcc_rich_location richloc (missing_close_brace_6_1);
+    bool added = richloc.add_location_if_nearby (matching_open_brace_3_1);
+    ASSERT_FALSE (added);
+    ASSERT_EQ (1, richloc.get_num_locations ());
+  }
 }
 
 /* Verify that we print fixits even if they only affect lines
@@ -2268,8 +4010,7 @@ test_diagnostic_show_locus_fixit_lines (const line_table_case &case_)
     richloc.add_fixit_insert_before (x, ".");
     richloc.add_fixit_replace (colon, "=");
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-                 " struct point origin = {x: 0.0,\n"
+    ASSERT_STREQ (" struct point origin = {x: 0.0,\n"
                  "                         ^\n"
                  "                        .=\n",
                  pp_formatted_text (dc.printer));
@@ -2289,8 +4030,7 @@ test_diagnostic_show_locus_fixit_lines (const line_table_case &case_)
     richloc.add_fixit_insert_before (y, ".");
     richloc.add_fixit_replace (colon, "=");
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-                 "FILENAME:3:24:\n"
+    ASSERT_STREQ ("FILENAME:3:24:\n"
                  "                        y\n"
                  "                        .\n"
                  "FILENAME:6:25:\n"
@@ -2299,6 +4039,28 @@ test_diagnostic_show_locus_fixit_lines (const line_table_case &case_)
                  "                         =\n",
                  pp_formatted_text (dc.printer));
   }
+
+  /* As above, but verify the behavior of multiple line spans
+     with line-numbering enabled.  */
+  {
+    const location_t y
+      = linemap_position_for_line_and_column (line_table, ord_map, 3, 24);
+    const location_t colon
+      = linemap_position_for_line_and_column (line_table, ord_map, 6, 25);
+    rich_location richloc (line_table, colon);
+    richloc.add_fixit_insert_before (y, ".");
+    richloc.add_fixit_replace (colon, "=");
+    test_diagnostic_context dc;
+    dc.show_line_numbers_p = true;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("    3 |                        y\n"
+                 "      |                        .\n"
+                 "......\n"
+                 "    6 |                         : 0.0};\n"
+                 "      |                         ^\n"
+                 "      |                         =\n",
+                 pp_formatted_text (dc.printer));
+  }
 }
 
 
@@ -2501,8 +4263,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     richloc.add_fixit_insert_after (")");
 
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-                 "   foo *f = (foo *)ptr->field;\n"
+    ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
                  "                   ^~~~~~~~~~\n"
                  "            -----------------\n"
                  "            const_cast<foo *> (ptr->field)\n",
@@ -2511,13 +4272,19 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     /* Unit-test the line_corrections machinery.  */
     ASSERT_EQ (3, richloc.get_num_fixit_hints ());
     const fixit_hint *hint_0 = richloc.get_fixit_hint (0);
-    ASSERT_EQ (column_range (12, 12), get_affected_columns (hint_0));
+    ASSERT_EQ (column_range (12, 12), get_affected_range (hint_0, CU_BYTES));
+    ASSERT_EQ (column_range (12, 12),
+                          get_affected_range (hint_0, CU_DISPLAY_COLS));
     ASSERT_EQ (column_range (12, 22), get_printed_columns (hint_0));
     const fixit_hint *hint_1 = richloc.get_fixit_hint (1);
-    ASSERT_EQ (column_range (18, 18), get_affected_columns (hint_1));
+    ASSERT_EQ (column_range (18, 18), get_affected_range (hint_1, CU_BYTES));
+    ASSERT_EQ (column_range (18, 18),
+                          get_affected_range (hint_1, CU_DISPLAY_COLS));
     ASSERT_EQ (column_range (18, 20), get_printed_columns (hint_1));
     const fixit_hint *hint_2 = richloc.get_fixit_hint (2);
-    ASSERT_EQ (column_range (29, 28), get_affected_columns (hint_2));
+    ASSERT_EQ (column_range (29, 28), get_affected_range (hint_2, CU_BYTES));
+    ASSERT_EQ (column_range (29, 28),
+                          get_affected_range (hint_2, CU_DISPLAY_COLS));
     ASSERT_EQ (column_range (29, 29), get_printed_columns (hint_2));
 
     /* Add each hint in turn to a line_corrections instance,
@@ -2528,6 +4295,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     /* The first replace hint by itself.  */
     lc.add_hint (hint_0);
     ASSERT_EQ (1, lc.m_corrections.length ());
+    ASSERT_EQ (column_range (12, 12), lc.m_corrections[0]->m_affected_bytes);
     ASSERT_EQ (column_range (12, 12), lc.m_corrections[0]->m_affected_columns);
     ASSERT_EQ (column_range (12, 22), lc.m_corrections[0]->m_printed_columns);
     ASSERT_STREQ ("const_cast<", lc.m_corrections[0]->m_text);
@@ -2537,6 +4305,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     lc.add_hint (hint_1);
     ASSERT_EQ (1, lc.m_corrections.length ());
     ASSERT_STREQ ("const_cast<foo *> (", lc.m_corrections[0]->m_text);
+    ASSERT_EQ (column_range (12, 18), lc.m_corrections[0]->m_affected_bytes);
     ASSERT_EQ (column_range (12, 18), lc.m_corrections[0]->m_affected_columns);
     ASSERT_EQ (column_range (12, 30), lc.m_corrections[0]->m_printed_columns);
 
@@ -2546,6 +4315,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     ASSERT_STREQ ("const_cast<foo *> (ptr->field)",
                  lc.m_corrections[0]->m_text);
     ASSERT_EQ (1, lc.m_corrections.length ());
+    ASSERT_EQ (column_range (12, 28), lc.m_corrections[0]->m_affected_bytes);
     ASSERT_EQ (column_range (12, 28), lc.m_corrections[0]->m_affected_columns);
     ASSERT_EQ (column_range (12, 41), lc.m_corrections[0]->m_printed_columns);
   }
@@ -2559,8 +4329,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     richloc.add_fixit_insert_after (")");
 
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-                 "   foo *f = (foo *)ptr->field;\n"
+    ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
                  "                   ^~~~~~~~~~\n"
                  "            -\n"
                  "            CAST (-\n"
@@ -2577,8 +4346,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     richloc.add_fixit_insert_after (")");
 
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-                 "   foo *f = (foo *)ptr->field;\n"
+    ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
                  "                   ^~~~~~~~~~\n"
                  "            -\n"
                  "            CST ( -\n"
@@ -2599,8 +4367,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     ASSERT_EQ (1, richloc.get_num_fixit_hints ());
 
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-                 "   foo *f = (foo *)ptr->field;\n"
+    ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
                  "                   ^~~~~~~~~~\n"
                  "            -------\n"
                  "            (bar *)\n",
@@ -2620,8 +4387,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
 
     /* But the corrections are.  */
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-                 "   foo *f = (foo *)ptr->field;\n"
+    ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
                  "                   ^~~~~~~~~~\n"
                  "            -----------------\n"
                  "            (longer *)(foo *)\n",
@@ -2639,8 +4405,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
        it would overlap with the second.
        Verify that they are printed as a single replacement.  */
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-                 "   foo *f = (foo *)ptr->field;\n"
+    ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
                  "                   ^~~~~~~~~~\n"
                  "            -------\n"
                  "            LONGER THAN THE CAST(foo *)TEST\n",
@@ -2648,6 +4413,328 @@ test_overlapped_fixit_printing (const line_table_case &case_)
   }
 }
 
+/* Multibyte-aware version of preceding tests.  See comments above
+   test_one_liner_simple_caret_utf8() too, we use the same two multibyte
+   characters here.  */
+
+static void
+test_overlapped_fixit_printing_utf8 (const line_table_case &case_)
+{
+  /* Create a tempfile and write some text to it.  */
+
+  const char *content
+    /* Display columns.
+       00000000000000000000000111111111111111111111111222222222222222223
+       12344444444555555556789012344444444555555556789012345678999999990  */
+    = "  f\xf0\x9f\x98\x82 *f = (f\xf0\x9f\x98\x82 *)ptr->field\xcf\x80;\n";
+    /* 00000000000000000000011111111111111111111112222222222333333333333
+       12344445555666677778901234566667777888899990123456789012333344445
+       Byte columns.  */
+
+  temp_source_file tmp (SELFTEST_LOCATION, ".C", content);
+  line_table_test ltt (case_);
+
+  const line_map_ordinary *ord_map
+    = linemap_check_ordinary (linemap_add (line_table, LC_ENTER, false,
+                                          tmp.get_filename (), 0));
+
+  linemap_line_start (line_table, 1, 100);
+
+  const location_t final_line_end
+    = linemap_position_for_line_and_column (line_table, ord_map, 6, 50);
+
+  /* Don't attempt to run the tests if column data might be unavailable.  */
+  if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
+    return;
+
+  /* A test for converting a C-style cast to a C++-style cast.  */
+  const location_t open_paren
+    = linemap_position_for_line_and_column (line_table, ord_map, 1, 14);
+  const location_t close_paren
+    = linemap_position_for_line_and_column (line_table, ord_map, 1, 22);
+  const location_t expr_start
+    = linemap_position_for_line_and_column (line_table, ord_map, 1, 23);
+  const location_t expr_finish
+    = linemap_position_for_line_and_column (line_table, ord_map, 1, 34);
+  const location_t expr = make_location (expr_start, expr_start, expr_finish);
+
+  /* Various examples of fix-it hints that aren't themselves consolidated,
+     but for which the *printing* may need consolidation.  */
+
+  /* Example where 3 fix-it hints are printed as one.  */
+  {
+    test_diagnostic_context dc;
+    rich_location richloc (line_table, expr);
+    richloc.add_fixit_replace (open_paren, "const_cast<");
+    richloc.add_fixit_replace (close_paren, "> (");
+    richloc.add_fixit_insert_after (")");
+
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("   f\xf0\x9f\x98\x82"
+                       " *f = (f\xf0\x9f\x98\x82"
+                                 " *)ptr->field\xcf\x80"
+                                               ";\n"
+                 "                   ^~~~~~~~~~~\n"
+                 "            ------------------\n"
+                 "            const_cast<f\xf0\x9f\x98\x82"
+                                           " *> (ptr->field\xcf\x80"
+                                                           ")\n",
+                 pp_formatted_text (dc.printer));
+
+    /* Unit-test the line_corrections machinery.  */
+    ASSERT_EQ (3, richloc.get_num_fixit_hints ());
+    const fixit_hint *hint_0 = richloc.get_fixit_hint (0);
+    ASSERT_EQ (column_range (14, 14), get_affected_range (hint_0, CU_BYTES));
+    ASSERT_EQ (column_range (12, 12),
+                          get_affected_range (hint_0, CU_DISPLAY_COLS));
+    ASSERT_EQ (column_range (12, 22), get_printed_columns (hint_0));
+    const fixit_hint *hint_1 = richloc.get_fixit_hint (1);
+    ASSERT_EQ (column_range (22, 22), get_affected_range (hint_1, CU_BYTES));
+    ASSERT_EQ (column_range (18, 18),
+                          get_affected_range (hint_1, CU_DISPLAY_COLS));
+    ASSERT_EQ (column_range (18, 20), get_printed_columns (hint_1));
+    const fixit_hint *hint_2 = richloc.get_fixit_hint (2);
+    ASSERT_EQ (column_range (35, 34), get_affected_range (hint_2, CU_BYTES));
+    ASSERT_EQ (column_range (30, 29),
+                          get_affected_range (hint_2, CU_DISPLAY_COLS));
+    ASSERT_EQ (column_range (30, 30), get_printed_columns (hint_2));
+
+    /* Add each hint in turn to a line_corrections instance,
+       and verify that they are consolidated into one correction instance
+       as expected.  */
+    line_corrections lc (tmp.get_filename (), 1);
+
+    /* The first replace hint by itself.  */
+    lc.add_hint (hint_0);
+    ASSERT_EQ (1, lc.m_corrections.length ());
+    ASSERT_EQ (column_range (14, 14), lc.m_corrections[0]->m_affected_bytes);
+    ASSERT_EQ (column_range (12, 12), lc.m_corrections[0]->m_affected_columns);
+    ASSERT_EQ (column_range (12, 22), lc.m_corrections[0]->m_printed_columns);
+    ASSERT_STREQ ("const_cast<", lc.m_corrections[0]->m_text);
+
+    /* After the second replacement hint, they are printed together
+       as a replacement (along with the text between them).  */
+    lc.add_hint (hint_1);
+    ASSERT_EQ (1, lc.m_corrections.length ());
+    ASSERT_STREQ ("const_cast<f\xf0\x9f\x98\x82 *> (",
+                 lc.m_corrections[0]->m_text);
+    ASSERT_EQ (column_range (14, 22), lc.m_corrections[0]->m_affected_bytes);
+    ASSERT_EQ (column_range (12, 18), lc.m_corrections[0]->m_affected_columns);
+    ASSERT_EQ (column_range (12, 30), lc.m_corrections[0]->m_printed_columns);
+
+    /* After the final insertion hint, they are all printed together
+       as a replacement (along with the text between them).  */
+    lc.add_hint (hint_2);
+    ASSERT_STREQ ("const_cast<f\xf0\x9f\x98\x82 *> (ptr->field\xcf\x80)",
+                 lc.m_corrections[0]->m_text);
+    ASSERT_EQ (1, lc.m_corrections.length ());
+    ASSERT_EQ (column_range (14, 34), lc.m_corrections[0]->m_affected_bytes);
+    ASSERT_EQ (column_range (12, 29), lc.m_corrections[0]->m_affected_columns);
+    ASSERT_EQ (column_range (12, 42), lc.m_corrections[0]->m_printed_columns);
+  }
+
+  /* Example where two are consolidated during printing.  */
+  {
+    test_diagnostic_context dc;
+    rich_location richloc (line_table, expr);
+    richloc.add_fixit_replace (open_paren, "CAST (");
+    richloc.add_fixit_replace (close_paren, ") (");
+    richloc.add_fixit_insert_after (")");
+
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("   f\xf0\x9f\x98\x82"
+                       " *f = (f\xf0\x9f\x98\x82"
+                                 " *)ptr->field\xcf\x80"
+                                               ";\n"
+                 "                   ^~~~~~~~~~~\n"
+                 "            -\n"
+                 "            CAST (-\n"
+                 "                  ) (         )\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Example where none are consolidated during printing.  */
+  {
+    test_diagnostic_context dc;
+    rich_location richloc (line_table, expr);
+    richloc.add_fixit_replace (open_paren, "CST (");
+    richloc.add_fixit_replace (close_paren, ") (");
+    richloc.add_fixit_insert_after (")");
+
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("   f\xf0\x9f\x98\x82"
+                       " *f = (f\xf0\x9f\x98\x82"
+                                 " *)ptr->field\xcf\x80"
+                                               ";\n"
+                 "                   ^~~~~~~~~~~\n"
+                 "            -\n"
+                 "            CST ( -\n"
+                 "                  ) (         )\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Example of deletion fix-it hints.  */
+  {
+    test_diagnostic_context dc;
+    rich_location richloc (line_table, expr);
+    richloc.add_fixit_insert_before (open_paren, "(bar\xf0\x9f\x98\x82 *)");
+    source_range victim = {open_paren, close_paren};
+    richloc.add_fixit_remove (victim);
+
+    /* This case is actually handled by fixit-consolidation,
+       rather than by line_corrections.  */
+    ASSERT_EQ (1, richloc.get_num_fixit_hints ());
+
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("   f\xf0\x9f\x98\x82"
+                       " *f = (f\xf0\x9f\x98\x82"
+                                 " *)ptr->field\xcf\x80"
+                                               ";\n"
+                 "                   ^~~~~~~~~~~\n"
+                 "            -------\n"
+                 "            (bar\xf0\x9f\x98\x82"
+                                   " *)\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Example of deletion fix-it hints that would overlap.  */
+  {
+    test_diagnostic_context dc;
+    rich_location richloc (line_table, expr);
+    richloc.add_fixit_insert_before (open_paren, "(long\xf0\x9f\x98\x82 *)");
+    source_range victim = {expr_start, expr_finish};
+    richloc.add_fixit_remove (victim);
+
+    /* These fixits are not consolidated.  */
+    ASSERT_EQ (2, richloc.get_num_fixit_hints ());
+
+    /* But the corrections are.  */
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("   f\xf0\x9f\x98\x82"
+                       " *f = (f\xf0\x9f\x98\x82"
+                                 " *)ptr->field\xcf\x80"
+                                               ";\n"
+                 "                   ^~~~~~~~~~~\n"
+                 "            ------------------\n"
+                 "            (long\xf0\x9f\x98\x82"
+                                    " *)(f\xf0\x9f\x98\x82"
+                                           " *)\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Example of insertion fix-it hints that would overlap.  */
+  {
+    test_diagnostic_context dc;
+    rich_location richloc (line_table, expr);
+    richloc.add_fixit_insert_before
+      (open_paren, "L\xf0\x9f\x98\x82NGER THAN THE CAST");
+    richloc.add_fixit_insert_after (close_paren, "TEST");
+
+    /* The first insertion is long enough that if printed naively,
+       it would overlap with the second.
+       Verify that they are printed as a single replacement.  */
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("   f\xf0\x9f\x98\x82"
+                       " *f = (f\xf0\x9f\x98\x82"
+                                 " *)ptr->field\xcf\x80"
+                                               ";\n"
+                 "                   ^~~~~~~~~~~\n"
+                 "            -------\n"
+                 "            L\xf0\x9f\x98\x82"
+                                "NGER THAN THE CAST(f\xf0\x9f\x98\x82"
+                                                      " *)TEST\n",
+                 pp_formatted_text (dc.printer));
+  }
+}
+
+/* Verify that the line_corrections machinery correctly prints
+   overlapping fixit-hints that have been added in the wrong
+   order.
+   Adapted from PR c/81405 seen on gcc.dg/init-excess-1.c*/
+
+static void
+test_overlapped_fixit_printing_2 (const line_table_case &case_)
+{
+  /* Create a tempfile and write some text to it.
+     ...000000000111111111122222222223333333333.
+     ...123456789012345678901234567890123456789.  */
+  const char *content
+    = ("int a5[][0][0] = { 1, 2 };\n");
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  line_table_test ltt (case_);
+
+  const line_map_ordinary *ord_map
+    = linemap_check_ordinary (linemap_add (line_table, LC_ENTER, false,
+                                          tmp.get_filename (), 0));
+
+  linemap_line_start (line_table, 1, 100);
+
+  const location_t final_line_end
+    = linemap_position_for_line_and_column (line_table, ord_map, 1, 100);
+
+  /* Don't attempt to run the tests if column data might be unavailable.  */
+  if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
+    return;
+
+  const location_t col_1
+    = linemap_position_for_line_and_column (line_table, ord_map, 1, 1);
+  const location_t col_20
+    = linemap_position_for_line_and_column (line_table, ord_map, 1, 20);
+  const location_t col_21
+    = linemap_position_for_line_and_column (line_table, ord_map, 1, 21);
+  const location_t col_23
+    = linemap_position_for_line_and_column (line_table, ord_map, 1, 23);
+  const location_t col_25
+    = linemap_position_for_line_and_column (line_table, ord_map, 1, 25);
+
+  /* Two insertions, in the wrong order.  */
+  {
+    rich_location richloc (line_table, col_20);
+    richloc.add_fixit_insert_before (col_23, "{");
+    richloc.add_fixit_insert_before (col_21, "}");
+
+    /* These fixits should be accepted; they can't be consolidated.  */
+    ASSERT_EQ (2, richloc.get_num_fixit_hints ());
+    const fixit_hint *hint_0 = richloc.get_fixit_hint (0);
+    ASSERT_EQ (column_range (23, 22), get_affected_range (hint_0, CU_BYTES));
+    ASSERT_EQ (column_range (23, 23), get_printed_columns (hint_0));
+    const fixit_hint *hint_1 = richloc.get_fixit_hint (1);
+    ASSERT_EQ (column_range (21, 20), get_affected_range (hint_1, CU_BYTES));
+    ASSERT_EQ (column_range (21, 21), get_printed_columns (hint_1));
+
+    /* Verify that they're printed correctly.  */
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ (" int a5[][0][0] = { 1, 2 };\n"
+                 "                    ^\n"
+                 "                     } {\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Various overlapping insertions, some occurring "out of order"
+     (reproducing the fix-it hints from PR c/81405).  */
+  {
+    test_diagnostic_context dc;
+    rich_location richloc (line_table, col_20);
+
+    richloc.add_fixit_insert_before (col_20, "{{");
+    richloc.add_fixit_insert_before (col_21, "}}");
+    richloc.add_fixit_insert_before (col_23, "{");
+    richloc.add_fixit_insert_before (col_21, "}");
+    richloc.add_fixit_insert_before (col_23, "{{");
+    richloc.add_fixit_insert_before (col_25, "}");
+    richloc.add_fixit_insert_before (col_21, "}");
+    richloc.add_fixit_insert_before (col_1, "{");
+    richloc.add_fixit_insert_before (col_25, "}");
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ (" int a5[][0][0] = { 1, 2 };\n"
+                 "                    ^\n"
+                 " {                  -----\n"
+                 "                    {{1}}}}, {{{2 }}\n",
+                 pp_formatted_text (dc.printer));
+  }
+}
+
 /* Insertion fix-it hint: adding a "break;" on a line by itself.  */
 
 static void
@@ -2678,13 +4765,29 @@ test_fixit_insert_containing_newline (const line_table_case &case_)
   {
     rich_location richloc (line_table, case_loc);
     richloc.add_fixit_insert_before (line_start, "      break;\n");
-    test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-                 "+      break;\n"
-                 "     case 'b':\n"
-                 "     ^~~~~~~~~\n",
-                 pp_formatted_text (dc.printer));
+
+    /* Without line numbers.  */
+    {
+      test_diagnostic_context dc;
+      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      ASSERT_STREQ ("       x = a;\n"
+                   "+      break;\n"
+                   "     case 'b':\n"
+                   "     ^~~~~~~~~\n",
+                   pp_formatted_text (dc.printer));
+    }
+
+    /* With line numbers.  */
+    {
+      test_diagnostic_context dc;
+      dc.show_line_numbers_p = true;
+      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      ASSERT_STREQ ("    2 |       x = a;\n"
+                   "  +++ |+      break;\n"
+                   "    3 |     case 'b':\n"
+                   "      |     ^~~~~~~~~\n",
+                   pp_formatted_text (dc.printer));
+    }
   }
 
   /* Verify that attempts to add text with a newline fail when the
@@ -2695,8 +4798,7 @@ test_fixit_insert_containing_newline (const line_table_case &case_)
     ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-                 "     case 'b':\n"
+    ASSERT_STREQ ("     case 'b':\n"
                  "     ^~~~~~~~~\n",
                  pp_formatted_text (dc.printer));
   }
@@ -2741,16 +4843,31 @@ test_fixit_insert_containing_newline_2 (const line_table_case &case_)
   if (putchar_finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
     return;
 
-  test_diagnostic_context dc;
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-               "FILENAME:1:1:\n"
-               "+#include <stdio.h>\n"
-               " test (int ch)\n"
-               "FILENAME:3:2:\n"
-               "  putchar (ch);\n"
-               "  ^~~~~~~\n",
-               pp_formatted_text (dc.printer));
+  {
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("FILENAME:1:1:\n"
+                 "+#include <stdio.h>\n"
+                 " test (int ch)\n"
+                 "FILENAME:3:2:\n"
+                 "  putchar (ch);\n"
+                 "  ^~~~~~~\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* With line-numbering, the line spans are close enough to be
+     consolidated, since it makes little sense to skip line 2.  */
+  {
+    test_diagnostic_context dc;
+    dc.show_line_numbers_p = true;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("  +++ |+#include <stdio.h>\n"
+                 "    1 | test (int ch)\n"
+                 "    2 | {\n"
+                 "    3 |  putchar (ch);\n"
+                 "      |  ^~~~~~~\n",
+                 pp_formatted_text (dc.printer));
+  }
 }
 
 /* Replacement fix-it hint containing a newline.
@@ -2787,8 +4904,7 @@ test_fixit_replace_containing_newline (const line_table_case &case_)
 
   test_diagnostic_context dc;
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-               " foo = bar ();\n"
+  ASSERT_STREQ (" foo = bar ();\n"
                "             ^\n",
                pp_formatted_text (dc.printer));
 }
@@ -2832,37 +4948,95 @@ test_fixit_deletion_affecting_newline (const line_table_case &case_)
 
   test_diagnostic_context dc;
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-               " foo = bar (\n"
+  ASSERT_STREQ (" foo = bar (\n"
                "          ~^\n"
                "       );\n"
                "       ~    \n",
                pp_formatted_text (dc.printer));
 }
 
+/* Verify that line numbers are correctly printed for the case of
+   a multiline range in which the width of the line numbers changes
+   (e.g. from "9" to "10").  */
+
+static void
+test_line_numbers_multiline_range ()
+{
+  /* Create a tempfile and write some text to it.  */
+  pretty_printer pp;
+  for (int i = 0; i < 20; i++)
+    /* .........0000000001111111.
+   .............1234567890123456.  */
+    pp_printf (&pp, "this is line %i\n", i + 1);
+  temp_source_file tmp (SELFTEST_LOCATION, ".txt", pp_formatted_text (&pp));
+  line_table_test ltt;
+
+  const line_map_ordinary *ord_map = linemap_check_ordinary
+    (linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 0));
+  linemap_line_start (line_table, 1, 100);
+
+  /* Create a multi-line location, starting at the "line" of line 9, with
+     a caret on the "is" of line 10, finishing on the "this" line 11.  */
+
+  location_t start
+    = linemap_position_for_line_and_column (line_table, ord_map, 9, 9);
+  location_t caret
+    = linemap_position_for_line_and_column (line_table, ord_map, 10, 6);
+  location_t finish
+    = linemap_position_for_line_and_column (line_table, ord_map, 11, 4);
+  location_t loc = make_location (caret, start, finish);
+
+  test_diagnostic_context dc;
+  dc.show_line_numbers_p = true;
+  dc.min_margin_width = 0;
+  gcc_rich_location richloc (loc);
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  ASSERT_STREQ (" 9 | this is line 9\n"
+               "   |         ~~~~~~\n"
+               "10 | this is line 10\n"
+               "   | ~~~~~^~~~~~~~~~\n"
+               "11 | this is line 11\n"
+               "   | ~~~~  \n",
+               pp_formatted_text (dc.printer));
+}
+
 /* Run all of the selftests within this file.  */
 
 void
 diagnostic_show_locus_c_tests ()
 {
+  test_line_span ();
+
   test_layout_range_for_single_point ();
   test_layout_range_for_single_line ();
   test_layout_range_for_multiple_lines ();
 
-  test_get_line_width_without_trailing_whitespace ();
+  for_each_line_table_case (test_layout_x_offset_display_utf8);
+
+  test_get_line_bytes_without_trailing_whitespace ();
 
   test_diagnostic_show_locus_unknown_location ();
 
   for_each_line_table_case (test_diagnostic_show_locus_one_liner);
+  for_each_line_table_case (test_diagnostic_show_locus_one_liner_utf8);
+  for_each_line_table_case (test_add_location_if_nearby);
   for_each_line_table_case (test_diagnostic_show_locus_fixit_lines);
   for_each_line_table_case (test_fixit_consolidation);
   for_each_line_table_case (test_overlapped_fixit_printing);
+  for_each_line_table_case (test_overlapped_fixit_printing_utf8);
+  for_each_line_table_case (test_overlapped_fixit_printing_2);
   for_each_line_table_case (test_fixit_insert_containing_newline);
   for_each_line_table_case (test_fixit_insert_containing_newline_2);
   for_each_line_table_case (test_fixit_replace_containing_newline);
   for_each_line_table_case (test_fixit_deletion_affecting_newline);
+
+  test_line_numbers_multiline_range ();
 }
 
 } // namespace selftest
 
 #endif /* #if CHECKING_P */
+
+#if __GNUC__ >= 10
+#  pragma GCC diagnostic pop
+#endif