]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb: pagination fix for emoji and unicode box output
authorAndrew Burgess <aburgess@redhat.com>
Mon, 12 Jan 2026 16:36:36 +0000 (16:36 +0000)
committerAndrew Burgess <aburgess@redhat.com>
Thu, 22 Jan 2026 15:00:16 +0000 (15:00 +0000)
I noticed that, on a 24 line terminal, the new unicode boxed hint text
was causing the pager to trigger unexpectedly:

  $ gdb -nx -nh
  GNU gdb (GDB) 18.0.50.20260107-git
  Copyright (C) 2026 Free Software Foundation, Inc.
  License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
  This is free software: you are free to change and redistribute it.
  There is NO WARRANTY, to the extent permitted by law.
  Type "show copying" and "show warranty" for details.
  This GDB was configured as "x86_64-pc-linux-gnu".
  Type "show configuration" for configuration details.
  For bug reporting instructions, please see:
  <https://www.gnu.org/software/gdb/bugs/>.

  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
  ┃ Find the GDB manual online at:                                            ┃
  ┃ http://www.gnu.org/software/gdb/documentation/.                           ┃
  ┃ For help, type "help".                                                    ┃
  ┃ Type "apropos <word>" to search for commands related to <word>.           ┃
  --Type <RET> for more, q to quit, c to continue without paging--

At this point there are 6 unused lines remaining in my terminal, so
the pager should not have triggered yet.

There are two problems here, both in pager_file::puts (in utils.c).
Lets start with the easy problem first.  When content is written to
the pager_file we process it within a loop looking for a newline
character.  We handle some special cases, but if none of them apply we
handle all general, printable, content with this block:

  else
    {
      m_wrap_buffer.push_back (*linebuffer);
      chars_printed++;
      linebuffer++;
    }

This copies one byte from LINEBUFFER to M_WRAP_BUFFER, and increments
CHARS_PRINTED.  The problem is that the unicode box characters are
multi-byte, this means we are over incrementing CHARS_PRINTED by
counting each byte of the unicode character as one output character.

GDB believes that the top line of the box is actually going to span
over multiple screen lines due to the large number of bytes within the
line.  In reality of course, the multi-byte characters fill exactly
one screen line.

I propose fixing this by making use of mbrlen to spot multi-byte
characters and count them as a single character.

If mbrlen returns anything less than 1 (which indicates a null
character, or an invalid character), then I just treat this as a
single byte character and continue as before.  This means if any
"weird" output is sent to the pager then it will still be printed.

The null wide character case shouldn't occur as the null wide
character is still all zeros, which the outer control loop in ::puts
should catch, so all I'm really concerned about is the invalid wide
character case.

Handling multi-byte wide characters does make things a little better,
but doesn't fix everything.  The pager still activates unnecessarily,
but just a little later.  On the same 80x24 terminal, the output is
now:

  $ gdb -nx -nh
  GNU gdb (GDB) 18.0.50.20260107-git
  Copyright (C) 2026 Free Software Foundation, Inc.
  License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
  This is free software: you are free to change and redistribute it.
  There is NO WARRANTY, to the extent permitted by law.
  Type "show copying" and "show warranty" for details.
  This GDB was configured as "x86_64-pc-linux-gnu".
  Type "show configuration" for configuration details.
  For bug reporting instructions, please see:
  <https://www.gnu.org/software/gdb/bugs/>.

  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
  ┃ Find the GDB manual online at:                                            ┃
  ┃ http://www.gnu.org/software/gdb/documentation/.                           ┃
  ┃ For help, type "help".                                                    ┃
  ┃ Type "apropos <word>" to search for commands related to <word>.           ┃
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
  --Type <RET> for more, q to quit, c to continue without paging--

We managed to get an extra line of output printed, but there is still
enough room on the terminal to print everything, so why is the pager
triggering?

The problem now is how we deal with lines that entirely fill the
terminal, and how we handle newlines.

Within the pager_file::puts inner loop we process input from
LINEBUFFER and copy it to the M_WRAP_BUFFER, incrementing
CHARS_PRINTED as we do.  This continues until we reach the end of
LINEBUFFER, or until we reach a newline within LINEBUFFER.

After each character is added to M_WRAP_BUFFER, we check CHARS_PRINTED
to see if we have filled a line.  If we have then we flush
M_WRAP_BUFFER and increment LINES_PRINTED.  If enough lines have now
been printed then we activate the pager.

Alternatively, if we encounter a newline character in LINEBUFFER then
we flush M_WRAP_BUFFER and increment LINES_PRINTED, then we re-enter
the inner loop, which includes performing a check to see if the pager
should trigger.

The problem here is that when we print the box, we entirely fill a
screen line, and then print the newline character.  When we print the
final non-newline character then this is enough to trigger the line
full logic, this flushes the line and increments LINES_PRINTED.  The
CHARS_PRINTED count is also reset to zero.

Then we print the newline.  This never enters the inner loop, but just
goes straight to the newline handling code, which increments
LINES_PRINTED and also resets CHARS_PRINTED to zero.

Notice that we've now incremented LINES_PRINTED twice.  This is the
cause of the premature pager activation; lines that are exactly one
screen width wide end up being double counted.

My initial thoughts when trying to fix this were to move the full line
check before the code which copies content from LINEBUFFER to
M_WRAP_BUFFER, inside the pager_file::puts inner loop.  This would
mean we only check for a full line when processing the next byte of
output after we've filled a screen line, but we'd never encounter this
check if the first byte after a full screen line was the newline
character, as in this case we'd never enter the inner loop.

And this does indeed fix the immediate problem, but I think, is still
not correct.

On an 80 character wide terminal, what we actually care about, is when
we try to add the 81st _printable_ character.  If the 81st character
was a tab then this doesn't wrap onto the next line.  Or if the 81st
character was \r, then this certainly doesn't wrap to a new line, it
just resets the current line.  And the same is true for the 82nd
character, and so on.  The only time we need to trigger a new screen
line is when we try to actually print something that will be displayed
to the user.

It turns out, I think, that we only want to check for a full line
inside the block that I mentioned above, the one I just updated to use
mbrlen.  This is the only place where printable content is copied from
LINEBUFFER into M_WRAP_BUFFER.

There are still some edge cases here that are not being handled
correctly, some unicode characters are non-printable, or stack on the
previous character, requiring zero width.  And even some of the basic
ASCII characters that we don't cover are non-printable.  But I'm
choosing to ignore all of these for now.  These cases were broken
before this patch, and continue to be broken afterwards.  Broken here
simply means that including these characters in GDB's output will
confuse the pager, likely resulting in the pager triggering too
early.

But for printable characters that are 1 terminal character wide,
things should now be a little better.  The pager will trigger only
when we try to add the first character that wraps onto the next screen
line.  In our original problem with the box, this means that when the
top border of the box is printed this will no longer cause an
increment of LINES_PRINTED.  When the newline is added then this does
finish off the current line and increments LINES_PRINTED as expected.
We now only increment LINES_PRINTED once for each line of the box,
rather than twice, and so the pager no longer needs to trigger during
startup.

To make the code cleaner, I moved the full line check into a new
function, pager_file::check_for_overfull_line(), and added comments in
the LINEBUFFER handling code to explain when the new function should
be called.

The test gdb.python/py-color-pagination.exp needed some updates after
this patch, the current expected output was tied to how the pager used
to operate.  Now that we defer starting a newline until we see some
printable characters GDB is better able to coalesce adjacent style
changes, this accounts for the large volume of changes to this test.

I've also added a couple of new tests to the
gdb.python/py-color-pagination.exp file.  An initial failed idea I had
for fixing this problem caused a bug such that empty lines would never
have triggered the pager, there's a new test that covers this case.
There's also a test that lines can exceed the screen width so long as
the extra content is non-printable; the test fills a line then prints
some tabs and a '\r' before filling the same line a second time.

There are also a couple of new pager self tests that I added.  I wrote
these because early on while investigating this issue, I thought I'd
spotted a bug in pager_file::wrap_here, so I wrote these tests to
expose that bug.  It turned not to be a bug, but a gap in my
understanding.  I think retaining these tests isn't going to hurt, and
increases coverage.

Approved-By: Tom Tromey <tom@tromey.com>
gdb/pager.h
gdb/testsuite/gdb.base/startup-hints.exp
gdb/testsuite/gdb.python/py-color-pagination.exp
gdb/testsuite/gdb.python/py-color-pagination.py
gdb/utils.c

index db8ddd8e4f5c2a239d0fb02d7530ce386a821fa0..a23e1bcc2de1c6e3c378438f68aa0d5c69d19085 100644 (file)
@@ -65,6 +65,19 @@ private:
 
   void prompt_for_continue ();
 
+  /* Check if enough characters have been printed such that the current
+     output line is full.  If this is the case then reset the characters
+     printed count to zero, and increment the lines printed count.  Call
+     prompt_for_continue if the screen is now full.
+
+     This should only be called at the point where we want to add new
+     printable output to the output stream, this defers triggering
+     pagination until we really need the additional screen space.
+
+     LINES_ALLOWED is the number of lines that can be printed before the
+     pager needs to activate.  */
+  void check_for_overfull_line (const unsigned int lines_allowed);
+
   /* Flush the wrap buffer to STREAM, if necessary.  */
   void flush_wrap_buffer ();
 
index b2948df9798c15456ebf7999672f3201b3063433..555011f50ba131a915149680de6946678f3858ef 100644 (file)
@@ -216,10 +216,50 @@ proc_with_prefix test_for_hint_with_width { width load_exec with_style } {
        "check for hint with width $width"
 }
 
+proc_with_prefix test_for_hint_pagination { with_style } {
+    global GDBFLAGS INTERNAL_GDBFLAGS gdb_prompt
+
+    save_vars { GDBFLAGS ::env(LC_ALL) } {
+       # Enable use of UTF-8 so the hint box can be displayed correctly.
+       setenv LC_ALL C.UTF-8
+
+       # Set the terminal size to width 80, height 24.
+       append GDBFLAGS " -eiex \"set width 80\" -eiex \"set height 24\""
+
+       gdb_exit
+
+       if { $with_style } {
+           with_ansi_styling_terminal {
+               gdb_spawn
+           }
+       } else {
+           gdb_spawn
+       }
+
+       # GDB is now starting up.  We want to ensure that we see the GDB
+       # prompt without first seeing a pagination prompt.
+       set saw_pagination_prompt false
+       gdb_test_multiple "" "check for no pagination" {
+           -re "Type <RET>" {
+               set saw_pagination_prompt true
+               # Send 'c' to allow GDB to continue startup.
+               send_gdb "c\n"
+               exp_continue
+           }
+           -re "$gdb_prompt $" {
+               gdb_assert { ! $saw_pagination_prompt } $gdb_test_name
+           }
+       }
+    }
+}
+
 save_vars { INTERNAL_GDBFLAGS } {
     set INTERNAL_GDBFLAGS [string map {"-q" ""} $INTERNAL_GDBFLAGS]
 
     foreach_with_prefix with_style { false true } {
+
+       test_for_hint_pagination $with_style
+
        foreach_with_prefix load_exec { false true } {
 
            # Width 0 actually means unlimited.  The other small sizes
index de09d1acb830242ed00320625a8978684c16f7d3..c51751ab15c6c29a142962fc801e38a18017e47d 100644 (file)
@@ -45,10 +45,15 @@ set white "(?:\033\\\[37${other_attr}m)"
 
 set any_color "(?:${black}|${red}|${green}|${yellow}|${blue}|${magenta}|${cyan}|${white})"
 
-# Run the command 'TYPE-fill MODE' which fills the screen with output and
-# triggers the pagination prompt.  Check that styling is applied correctly
-# to the output.
-proc test_pagination { type mode } {
+# Run CMD which produces some styled output via a custom Python
+# command.  The styled output will trigger pagination, ensure that the
+# pagination prompt is not styled.
+#
+# When RESET_AFTER_PRINT is true we expect the Python output to reset
+# back to the default style after each block of output, and when it is
+# false, Python will only restore the default style once before
+# returning to the GDB prompt.
+proc test_pagination { cmd reset_after_print } {
 
     # Start with a fresh GDB, but enable color support.
     with_ansi_styling_terminal {
@@ -60,30 +65,33 @@ proc test_pagination { type mode } {
     gdb_test_no_output "set width 80"
     gdb_test_no_output "set height 15"
 
+    if { $reset_after_print } {
+       set prompt_prefix ""
+       set output_suffix "\033\\\[m"
+    } else {
+       set prompt_prefix "\033\\\[m\r\n"
+       set output_suffix ""
+    }
+
     set saw_bad_color_handling false
-    set expected_restore_color ""
+    set expected_color ""
     set last_color ""
-    gdb_test_multiple "$type-fill $mode" "" {
-       -re "^$type-fill $mode\r\n" {
+    gdb_test_multiple "$cmd" "" {
+       -re "^[string_to_regexp $cmd]\r\n" {
            exp_continue
        }
 
-       -re "^(${::any_color})(${::any_color})$::str" {
-           # After a continuation prompt GDB will restore the previous
-           # color, and then we immediately switch to a new color.
-           set restored_color $expect_out(1,string)
-           if { $restored_color ne $expected_restore_color } {
+       -re "^(${::any_color})$::str${output_suffix}" {
+           set color $expect_out(1,string)
+
+           # When EXPECTED_COLOR is not the empty string, then this
+           # is the color that was in place prior to the pagination
+           # prompt, check that this same color was restored before
+           # printing the next output.
+           if { $expected_color ne "" && $color ne $expected_color } {
                set saw_bad_color_handling true
            }
-           set last_color $expect_out(2,string)
-           exp_continue
-       }
-
-       -re "^(${::any_color})$::str" {
-           # This pattern matches printing STR in all cases that are not
-           # immediately after a pagination prompt.  In this case there is
-           # a single escape sequence to set the color.
-           set last_color $expect_out(1,string)
+           set expected_color ""
            exp_continue
        }
 
@@ -95,25 +103,13 @@ proc test_pagination { type mode } {
            exp_continue
        }
 
-       -re "^\033\\\[m$::pagination_prompt$" {
-           # After a pagination prompt we expect GDB to restore the last
-           # color.
-           set expected_restore_color $last_color
-
-           # Send '\n' to view more output.
-           send_gdb "\n"
-           exp_continue
-       }
-
-       -re "^$::pagination_prompt$" {
-           # After a pagination prompt we expect GDB to restore the last
-           # color.
-           set expected_restore_color $last_color
-
-           # If we didn't see a color reset sequence before the pagination
-           # prompt, then the prompt will have been printed in the wrong
-           # color, this is a GDB bug.
-           set saw_bad_color_handling true
+       -re "^(${::any_color})\033\\\[m$::pagination_prompt$" {
+           # After a pagination prompt we expect GDB to restore the
+           # last color that was in use, so record it now.  The
+           # regexp above is also checking that styling is disabled
+           # before the pagination prompt is displayed, that is a
+           # critical part of this test.
+           set expected_color $expect_out(1,string)
 
            # Send '\n' to view more output.
            send_gdb "\n"
@@ -125,70 +121,112 @@ proc test_pagination { type mode } {
            exp_continue
        }
 
-       -re "^\033\\\[m\r\n$::gdb_prompt $" {
+       -re "^${prompt_prefix}$::gdb_prompt $" {
            gdb_assert { !$saw_bad_color_handling } $gdb_test_name
        }
     }
 }
 
-# Run the command 'style-fill-v2' which fills the screen with output and
-# triggers the pagination prompt.  Check that styling is applied correctly
-# to the output.  This v2 command is exercising passing a style to
-# gdb.write() rather than passing the escape sequence for the style.
-proc test_pagination_v2 { } {
-    set saw_bad_color_handling false
-    set expected_restore_color ""
-    set last_color ""
-    gdb_test_multiple "style-fill-v2" "" {
-       -re "^style-fill-v2\r\n" {
+# Use a custom command to emit blocks of empty lines.  Check when the
+# pagination prompt is triggered.
+proc_with_prefix test_blank_line_pagination {} {
+    clean_restart
+
+    gdb_test_no_output "source $::pyfile" "source the script"
+
+    gdb_test_no_output "set width 80"
+    gdb_test_no_output "set height 15"
+
+    foreach i { 13 14 } {
+       gdb_test "empty-lines $i" \
+           [multi_line {*}[lrepeat $i ""]] \
+           "$i blank lines doesn't trigger pagination"
+    }
+
+    set count 0
+    gdb_test_multiple "empty-lines 15" "15 blank lines triggers pagination" {
+       -re "^empty-lines 15\r\n" {
            exp_continue
        }
-
-       -re "^(${::any_color}\033\\\[m)(${::any_color})$::str\033\\\[m" {
-           # After a continuation prompt GDB will restore the previous
-           # color, and then we immediately switch to a new color.
-           set restored_color $expect_out(1,string)
-           if { $restored_color ne $expected_restore_color } {
-               set saw_bad_color_handling true
-           }
-           set last_color $expect_out(2,string)
+       -re "^\r\n" {
+           incr count
            exp_continue
        }
-
-       -re "^(${::any_color})$::str\033\\\[m" {
-           # This pattern matches printing STR in all cases that are not
-           # immediately after a pagination prompt.  In this case there is
-           # a single escape sequence to set the color.
-           set last_color $expect_out(1,string)
+       -re "^$::pagination_prompt$" {
+           gdb_assert { $count == 14 } \
+               "saw 14 lines before pagination"
+           set count 0
+           send_gdb "c\n"
+           exp_continue
+       }
+       -re "^c\r\n" {
            exp_continue
        }
+       -re "^$::gdb_prompt $" {
+           gdb_assert { $count == 1 } $gdb_test_name
+       }
+    }
+}
 
-       -re "^$::pagination_prompt$" {
-           # After a pagination prompt we expect GDB to restore the last
-           # color, but this will then be disabled due to a styled
-           # gdb.write emitting a return to default style escape sequence.
-           set expected_restore_color "$last_color\033\[m"
+# Use a custom command to emit blocks of very full lines.  The lines
+# consist of a full screen width of one character followed by a full
+# screen width of tab characters, followed by a '\r', then another
+# screen width full of a different printable character.
+#
+# Due to the '\r' each of these very full lines should only occupy a
+# single screen line.  We run the test with different numbers of lines
+# being printed, and check when (if at all) the pager triggers.
+proc_with_prefix test_full_line_pagination {} {
+    clean_restart
 
-           # Send '\n' to view more output.
-           send_gdb "\n"
+    gdb_test_no_output "source $::pyfile" "source the script"
+
+    gdb_test_no_output "set width 80"
+    gdb_test_no_output "set height 15"
+
+    set line_re "\\.{80}\t{80}\r\\+{80}"
+
+    foreach i { 13 14 } {
+       gdb_test "full-lines 80 $i" \
+           [multi_line {*}[lrepeat $i $line_re]] \
+           "$i full lines didn't trigger pagination"
+    }
+
+    set count 0
+    gdb_test_multiple "full-lines 80 15" "15 full lines triggers pagination" {
+       -re "^full-lines 80 15\r\n" {
            exp_continue
        }
-
-       -re "^\r\n" {
-           # The matches the newline sent to the continuation prompt.
+       -re "^$line_re\r\n" {
+           incr count
+           exp_continue
+       }
+       -re "^$::pagination_prompt$" {
+           gdb_assert { $count == 14 } \
+               "saw 14 lines before pagination"
+           set count 0
+           send_gdb "c\n"
+           exp_continue
+       }
+       -re "^c\r\n" {
            exp_continue
        }
-
        -re "^$::gdb_prompt $" {
-           gdb_assert { !$saw_bad_color_handling } $gdb_test_name
+           gdb_assert { $count == 1 } $gdb_test_name
        }
     }
 }
 
 foreach_with_prefix type { color style } {
     foreach_with_prefix mode { write print } {
-       test_pagination $type $mode
+       set cmd "$type-fill $mode"
+       test_pagination $cmd false
     }
 }
 
-test_pagination_v2
+with_test_prefix "style-fill-v2" {
+    test_pagination "style-fill-v2" true
+}
+
+test_blank_line_pagination
+test_full_line_pagination
index 9419e2ab98b4ab281188a07e72d25851ff8a477a..0c160d6bb295380fedc690e70f53e555ac42cd42 100644 (file)
@@ -77,6 +77,30 @@ class StyleTester2(gdb.Command):
         gdb.write("\n")
 
 
+class EmptyLines(gdb.Command):
+    def __init__(self):
+        super().__init__("empty-lines", gdb.COMMAND_USER)
+
+    def invoke(self, args, from_tty):
+        count = int(args)
+        for i in range(0, count):
+            print("")
+
+
+class FullLines(gdb.Command):
+    def __init__(self):
+        super().__init__("full-lines", gdb.COMMAND_USER)
+
+    def invoke(self, args, from_tty):
+        argv = gdb.string_to_argv(args)
+        width = int(argv[0])
+        height = int(argv[1])
+        for i in range(0, height):
+            print(("." * width) + ("\t" * width) + "\r" + ("+" * width))
+
+
 ColorTester()
 StyleTester()
 StyleTester2()
+EmptyLines()
+FullLines()
index 42d0c7de2d4fc4d7e0b83f7a90ebe4d1af7c1ad8..ceea8317dc5414a573cce176402e46828510e96a 100644 (file)
@@ -1595,6 +1595,83 @@ begin_line (void)
     }
 }
 
+/* See pager.h.  */
+
+void
+pager_file::check_for_overfull_line (const unsigned int lines_allowed)
+{
+  if (chars_printed >= chars_per_line)
+    {
+      unsigned int save_chars = chars_printed;
+
+      /* If we change the style, below, we'll want to reset it
+        before continuing to print.  If there is no wrap
+        column, then we'll only reset the style if the pager
+        prompt is given; and to avoid emitting style
+        sequences in the middle of a run of text, we track
+        this as well.  */
+      ui_file_style save_style = m_applied_style;
+      bool did_paginate = false;
+
+      chars_printed = 0;
+      lines_printed++;
+      if (m_wrap_column)
+       {
+         /* We are about to insert a newline at an historic
+            location in the WRAP_BUFFER.  Before we do we want to
+            restore the default style.  To know if we actually
+            need to insert an escape sequence we must restore the
+            current applied style to how it was at the WRAP_COLUMN
+            location.  */
+         m_applied_style = m_wrap_style;
+         this->set_stream_style (ui_file_style ());
+
+         /* If we aren't actually wrapping, don't output
+            newline -- if chars_per_line is right, we
+            probably just overflowed anyway; if it's wrong,
+            let us keep going.  */
+         m_stream->puts ("\n");
+       }
+      else
+       this->flush_wrap_buffer ();
+
+      /* Possible new page.  Note that
+        PAGINATION_DISABLED_FOR_COMMAND might be set during
+        this loop, so we must continue to check it here.  */
+      if (pagination_enabled
+         && !pagination_disabled_for_command
+         && lines_printed >= lines_allowed)
+       {
+         prompt_for_continue ();
+         did_paginate = true;
+       }
+
+      /* Now output indentation and wrapped string.  */
+      if (m_wrap_column)
+       {
+         m_stream->puts (n_spaces (m_wrap_indent));
+
+         /* Having finished inserting the wrapping we should
+            restore the style as it was at the WRAP_COLUMN.  */
+         this->set_stream_style (m_wrap_style);
+
+         /* The WRAP_BUFFER will still contain content, and that
+            content might set some alternative style.  Restore
+            APPLIED_STYLE as it was before we started wrapping,
+            this reflects the current style for the last character
+            in WRAP_BUFFER.  */
+         m_applied_style = save_style;
+
+         /* Note that this can set chars_printed > chars_per_line
+            if we are printing a long string.  */
+         chars_printed = m_wrap_indent + (save_chars - m_wrap_column);
+         m_wrap_column = 0;    /* And disable fancy wrap */
+       }
+      else if (did_paginate)
+       this->emit_style_escape (save_style);
+    }
+}
+
 void
 pager_file::puts (const char *linebuffer)
 {
@@ -1641,23 +1718,33 @@ pager_file::puts (const char *linebuffer)
 
       while (*linebuffer != '\0' && *linebuffer != '\n')
        {
-         int skip_bytes;
-
          /* Print a single line.  */
          if (*linebuffer == '\t')
            {
+             /* This does adjust CHARS_PRINTED, but doesn't count as
+                printable content, so check_for_overfull_line is not
+                called.
+
+                If a tab is printed at the end of an already full line,
+                then the tab does not wrap onto the next line.  We could
+                cap CHARS_PRINTED to CHARS_PER_LINE, but we don't gain
+                anything from doing that.  */
+
              m_wrap_buffer.push_back ('\t');
+
              /* Shifting right by 3 produces the number of tab stops
                 we have already passed, and then adding one and
                 shifting left 3 advances to the next tab stop.  */
              chars_printed = ((chars_printed >> 3) + 1) << 3;
              linebuffer++;
            }
-         else if (*linebuffer == '\033'
+         else if (int skip_bytes;
+                  *linebuffer == '\033'
                   && skip_ansi_escape (linebuffer, &skip_bytes))
            {
              /* We don't consider escape sequences as characters, so we
-                don't increment chars_printed here.  */
+                don't increment chars_printed here, and we don't need to
+                call check_for_overfull_line.  */
 
              /* This style sequence might not set every style attribute,
                 so start with the currently applied style, and update
@@ -1681,86 +1768,42 @@ pager_file::puts (const char *linebuffer)
            }
          else if (*linebuffer == '\r')
            {
+             /* This restarts the current line, it doesn't start a new
+                line, calling check_for_overfull_line is not needed as,
+                regardless of the current line state, the line will be
+                considered empty once this block has finished.  */
              m_wrap_buffer.push_back (*linebuffer);
              chars_printed = 0;
              linebuffer++;
            }
          else
            {
-             m_wrap_buffer.push_back (*linebuffer);
+             /* We are about to add some printable content to
+                M_WRAP_BUFFER.  Check to see if the current line is full,
+                and if we should start a new line.  This might trigger
+                pagination.  */
+             this->check_for_overfull_line (lines_allowed);
+
+             /* Check if we are at the start of a multi-byte character.
+                mbrlen returns the number of bytes the character occupies.
+                If it returns -1 (invalid) or 0 (null), we fall back to
+                treating this as a single byte character.  */
+             mbstate_t mb;
+             memset (&mb, 0, sizeof mb);
+             int len = mbrlen (linebuffer, MB_CUR_MAX, &mb);
+             if (len < 1)
+               len = 1;
+
+             /* Append to the output stream as many bytes from LINEBUFFER
+                as this character uses.  This should be fine so long as we
+                trust mbrlen.  */
+             m_wrap_buffer.append (linebuffer, len);
+             linebuffer += len;
+
+             /* For now assume that every multi-byte character that GDB
+                prints is only one character wide.  If this ever changes
+                then we need to consider something like wcwidth here.  */
              chars_printed++;
-             linebuffer++;
-           }
-
-         if (chars_printed >= chars_per_line)
-           {
-             unsigned int save_chars = chars_printed;
-
-             /* If we change the style, below, we'll want to reset it
-                before continuing to print.  If there is no wrap
-                column, then we'll only reset the style if the pager
-                prompt is given; and to avoid emitting style
-                sequences in the middle of a run of text, we track
-                this as well.  */
-             ui_file_style save_style = m_applied_style;
-             bool did_paginate = false;
-
-             chars_printed = 0;
-             lines_printed++;
-             if (m_wrap_column)
-               {
-                 /* We are about to insert a newline at an historic
-                    location in the WRAP_BUFFER.  Before we do we want to
-                    restore the default style.  To know if we actually
-                    need to insert an escape sequence we must restore the
-                    current applied style to how it was at the WRAP_COLUMN
-                    location.  */
-                 m_applied_style = m_wrap_style;
-                 this->set_stream_style (ui_file_style ());
-
-                 /* If we aren't actually wrapping, don't output
-                    newline -- if chars_per_line is right, we
-                    probably just overflowed anyway; if it's wrong,
-                    let us keep going.  */
-                 m_stream->puts ("\n");
-               }
-             else
-               this->flush_wrap_buffer ();
-
-             /* Possible new page.  Note that
-                PAGINATION_DISABLED_FOR_COMMAND might be set during
-                this loop, so we must continue to check it here.  */
-             if (pagination_enabled
-                 && !pagination_disabled_for_command
-                 && lines_printed >= lines_allowed)
-               {
-                 prompt_for_continue ();
-                 did_paginate = true;
-               }
-
-             /* Now output indentation and wrapped string.  */
-             if (m_wrap_column)
-               {
-                 m_stream->puts (n_spaces (m_wrap_indent));
-
-                 /* Having finished inserting the wrapping we should
-                    restore the style as it was at the WRAP_COLUMN.  */
-                 this->set_stream_style (m_wrap_style);
-
-                 /* The WRAP_BUFFER will still contain content, and that
-                    content might set some alternative style.  Restore
-                    APPLIED_STYLE as it was before we started wrapping,
-                    this reflects the current style for the last character
-                    in WRAP_BUFFER.  */
-                 m_applied_style = save_style;
-
-                 /* Note that this can set chars_printed > chars_per_line
-                    if we are printing a long string.  */
-                 chars_printed = m_wrap_indent + (save_chars - m_wrap_column);
-                 m_wrap_column = 0;    /* And disable fancy wrap */
-               }
-             else if (did_paginate)
-               this->emit_style_escape (save_style);
            }
        }
 
@@ -1817,6 +1860,27 @@ test_pager ()
   pager.puts ("bbbbbbbbbbbb\n");
 
   SELF_CHECK (strfile->string () == "aaaaaaaaaaaa\n  bbbbbbbbbbbb\n");
+
+  strfile->clear ();
+  pager.puts ("aaaaaaaaaaaaaaa");
+  pager.wrap_here (2);
+  pager.puts ("bbbbb\n");
+
+  SELF_CHECK (strfile->string () == "aaaaaaaaaaaaaaa\n  bbbbb\n");
+
+  strfile->clear ();
+  pager.puts ("aaaaaaaaaaaaaaa");
+  pager.wrap_here (10);
+  pager.puts ("bbbbb");
+  pager.wrap_here (10);
+  pager.puts ("ccccc");
+  pager.wrap_here (10);
+  pager.puts ("ddddd\n");
+
+  SELF_CHECK (strfile->string () == ("aaaaaaaaaaaaaaa\n"
+                                    "          bbbbb\n"
+                                    "          ccccc\n"
+                                    "          ddddd\n"));
 }
 
 #endif /* GDB_SELF_TEST */