erase_source_content ();
   else
     {
+      validate_scroll_offsets ();
       update_breakpoint_info (nullptr, false);
       show_source_content ();
       update_exec_info ();
     }
 }
 
+/* See tui-winsource.h.  */
+
+void
+tui_source_window_base::puts_to_pad_with_skip (const char *string, int skip)
+{
+  gdb_assert (m_pad.get () != nullptr);
+  WINDOW *w = m_pad.get ();
+
+  while (skip > 0)
+    {
+      const char *next = strpbrk (string, "\033");
+
+      /* Print the plain text prefix.  */
+      size_t n_chars = next == nullptr ? strlen (string) : next - string;
+      if (n_chars > 0)
+       {
+         if (skip > 0)
+           {
+             if (skip < n_chars)
+               {
+                 string += skip;
+                 n_chars -= skip;
+                 skip = 0;
+               }
+             else
+               {
+                 skip -= n_chars;
+                 string += n_chars;
+                 n_chars = 0;
+               }
+           }
+
+         if (n_chars > 0)
+           {
+             std::string copy (string, n_chars);
+             tui_puts (copy.c_str (), w);
+           }
+       }
+
+      /* We finished.  */
+      if (next == nullptr)
+       break;
+
+      gdb_assert (*next == '\033');
+
+      int n_read;
+      if (skip_ansi_escape (next, &n_read))
+       {
+         std::string copy (next, n_read);
+         tui_puts (copy.c_str (), w);
+         next += n_read;
+       }
+      else
+       gdb_assert_not_reached ("unhandled escape");
+
+      string = next;
+    }
+
+  if (*string != '\0')
+    tui_puts (string, w);
+}
 
 /* Redraw the complete line of a source or disassembly window.  */
 void
     tui_set_reverse_mode (m_pad.get (), true);
 
   wmove (m_pad.get (), lineno, 0);
-  tui_puts (line->line.c_str (), m_pad.get ());
+  puts_to_pad_with_skip (line->line.c_str (), m_pad_offset);
+
   if (line->is_exec_point)
     tui_set_reverse_mode (m_pad.get (), false);
 }
      the screen, potentially creating a flicker.  */
   wnoutrefresh (handle.get ());
 
-  int pad_width = std::max (m_max_length, width);
-  int left_margin = 1 + TUI_EXECINFO_SIZE + extra_margin ();
-  int view_width = width - left_margin - 1;
-  int pad_x = std::min (pad_width - view_width, m_horizontal_offset);
-  /* Ensure that an equal number of scrolls will work if the user
-     scrolled beyond where we clip.  */
-  m_horizontal_offset = pad_x;
+  int pad_width = getmaxx (m_pad.get ());
+  int left_margin = this->left_margin ();
+  int view_width = this->view_width ();
+  int content_width = m_max_length;
+  int pad_x = m_horizontal_offset - m_pad_offset;
+
+  gdb_assert (m_pad_offset >= 0);
+  gdb_assert (m_horizontal_offset + view_width
+             <= std::max (content_width, view_width));
+  gdb_assert (pad_x >= 0);
+  gdb_assert (m_horizontal_offset >= 0);
+
+  /* This function can be called before the pad has been allocated, this
+     should only occur during the initial startup.  In this case the first
+     condition in the following asserts will not be true, but the nullptr
+     check will.  */
+  gdb_assert (pad_width > 0 || m_pad.get () == nullptr);
+  gdb_assert (pad_x + view_width <= pad_width || m_pad.get () == nullptr);
+
   prefresh (m_pad.get (), 0, pad_x, y + 1, x + left_margin,
            y + m_content.size (), x + left_margin + view_width - 1);
 }
 
   check_and_display_highlight_if_needed ();
 
-  int pad_width = std::max (m_max_length, width);
-  if (m_pad == nullptr || pad_width > getmaxx (m_pad.get ())
-      || m_content.size () > getmaxy (m_pad.get ()))
-    m_pad.reset (newpad (m_content.size (), pad_width));
+  /* The pad should be at least as wide as the window, but ideally, as wide
+     as the content, however, for some very wide content this might not be
+     possible.  */
+  int required_pad_width = std::max (m_max_length, width);
+  int required_pad_height = m_content.size ();
+
+  /* If the required pad width is wider than the previously requested pad
+     width, then we might want to grow the pad.  */
+  if (required_pad_width > m_pad_requested_width
+      || required_pad_height > getmaxy (m_pad.get ()))
+    {
+      /* The current pad width.  */
+      int pad_width = m_pad == nullptr ? 0 : getmaxx (m_pad.get ());
+
+      gdb_assert (pad_width <= m_pad_requested_width);
+
+      /* If the current pad width is smaller than the previously requested
+        pad width, then this means we previously failed to allocate a
+        bigger pad.  There's no point asking again, so we'll just make so
+        with the pad we currently have.  */
+      if (pad_width == m_pad_requested_width
+         || required_pad_height > getmaxy (m_pad.get ()))
+       {
+         pad_width = required_pad_width;
+
+         do
+           {
+             /* Try to allocate a new pad.  */
+             m_pad.reset (newpad (required_pad_height, pad_width));
+
+             if (m_pad == nullptr)
+               {
+                 int reduced_width = std::max (pad_width / 2, width);
+                 if (reduced_width == pad_width)
+                   error (_("failed to setup source window"));
+                 pad_width = reduced_width;
+               }
+           }
+         while (m_pad == nullptr);
+       }
+
+      m_pad_requested_width = required_pad_width;
+    }
 
+  gdb_assert (m_pad != nullptr);
   werase (m_pad.get ());
   for (int lineno = 0; lineno < m_content.size (); lineno++)
     show_source_line (lineno);
   update_source_window_as_is (m_gdbarch, sal);
 }
 
+/* See tui-winsource.h.  */
+
+bool
+tui_source_window_base::validate_scroll_offsets ()
+{
+  int original_pad_offset = m_pad_offset;
+
+  if (m_horizontal_offset < 0)
+    m_horizontal_offset = 0;
+
+  int content_width = m_max_length;
+  int pad_width = getmaxx (m_pad.get ());
+  int view_width = this->view_width ();
+
+  if (m_horizontal_offset + view_width > content_width)
+    m_horizontal_offset = std::max (content_width - view_width, 0);
+
+  if ((m_horizontal_offset + view_width) > (m_pad_offset + pad_width))
+    {
+      m_pad_offset = std::min (m_horizontal_offset, content_width - pad_width);
+      m_pad_offset = std::max (m_pad_offset, 0);
+    }
+  else if (m_horizontal_offset < m_pad_offset)
+    m_pad_offset = std::max (m_horizontal_offset + view_width - pad_width, 0);
+
+  gdb_assert (m_pad_offset >= 0);
+  return (original_pad_offset != m_pad_offset);
+}
+
 /* Scroll the source forward or backward horizontally.  */
 
 void
 {
   if (!m_content.empty ())
     {
-      int offset = m_horizontal_offset + num_to_scroll;
-      if (offset < 0)
-       offset = 0;
-      m_horizontal_offset = offset;
+      m_horizontal_offset += num_to_scroll;
+
+      if (validate_scroll_offsets ())
+       show_source_content ();
+
       refresh_window ();
     }
 }
 
   /* Used for horizontal scroll.  */
   int m_horizontal_offset = 0;
 
+  /* Check that the current values of M_HORIZONTAL_OFFSET and M_PAD_OFFSET
+     make sense given the current M_MAX_LENGTH (content width), WIDTH
+     (window size), and window margins.  After calling this function
+     M_HORIZONTAL_OFFSET and M_PAD_OFFSET might have been adjusted to
+     reduce unnecessary whitespace on the right side of the window.
+
+     If M_PAD_OFFSET is adjusted then this function returns true
+     indicating that the pad contents need to be reloaded by calling
+     show_source_content.  If M_PAD_OFFSET is not adjusted then this
+     function returns false, the window contents might still need
+     redrawing if M_HORIZONTAL_OFFSET was adjusted, but right now, this
+     function is only called in contexts where the window is going to be
+     redrawn anyway.  */
+  bool validate_scroll_offsets ();
+
+  /* Return the size of the left margin space, this is the space used to
+     display things like breakpoint markers.  */
+  int left_margin () const
+  { return 1 + TUI_EXECINFO_SIZE + extra_margin (); }
+
+  /* Return the width of the area that is available for window content.
+     This is the window width minus the borders and the left margin, which
+     is used for displaying things like breakpoint markers.  */
+  int view_width () const
+  { return width - left_margin () - 1; }
+
   void show_source_content ();
 
+  /* Write STRING to the window M_PAD, but skip the first SKIP printable
+     characters.  Any escape sequences within the first SKIP characters are
+     still processed though.  This means if we have this string:
+
+     "\033[31mABCDEFGHIJKLM\033[0m"
+
+     and call this function with a skip value of 3, then we effectively
+     write this string to M_PAD:
+
+     "\033[31mDEFGHIJKLM\033[0m"
+
+     the initial escape that sets the color will still be applied.  */
+  void puts_to_pad_with_skip (const char *string, int skip);
+
   /* Called when the user "set style enabled" setting is changed.  */
   void style_changed ();
 
   /* A token used to register and unregister an observer.  */
   gdb::observers::token m_observable;
 
-  /* Pad used to display fixme mumble  */
+  /* Pad to hold some, or all, of the window contents.  Content is then
+     copied from this pad to the screen as the user scrolls horizontally,
+     this avoids the need to recalculate the screen contents each time the
+     user does a horizontal scroll.  */
   std::unique_ptr<WINDOW, curses_deleter> m_pad;
+
+  /* When M_PAD was allocated, this holds the width that was initially
+     asked for.  If we ask for a very large pad then the allocation may
+     fail, and we might instead allocate a narrower pad.  */
+  int m_pad_requested_width = 0;
+
+  /* If M_PAD is not as wide as the content (so less than M_MAX_LENGTH)
+     then this value indicates the offset at which the pad contents begin.  */
+  int m_pad_offset = 0;
 };