[wiki] [sites] [ST][PATCH] Bug fix in scrollback reflow standalone extended. || Milos Nikic

From: <git_AT_suckless.org>
Date: Mon, 23 Feb 2026 19:40:53 +0100

commit 04f6d9c4a2ab4449cac975afc2ea1fffc2f9c75c
Author: Milos Nikic <nikic.milos_AT_gmail.com>
Date: Mon Feb 23 10:38:47 2026 -0800

    [ST][PATCH] Bug fix in scrollback reflow standalone extended.

diff --git a/st.suckless.org/patches/scrollback-reflow-standalone/index.md b/st.suckless.org/patches/scrollback-reflow-standalone/index.md
index 1e80ba71..e2e20405 100644
--- a/st.suckless.org/patches/scrollback-reflow-standalone/index.md
+++ b/st.suckless.org/patches/scrollback-reflow-standalone/index.md
_AT_@ -126,7 +126,7 @@ No content is clipped or lost; only wrapping changes.
 Download
 --------
 * [st-scrollback-reflow-standalone-0.9.3.diff](st-scrollback-reflow-standalone-0.9.3.diff)
-* [st-scrollback-reflow-standalone-extended-0.9.3.diff](st-scrollback-reflow-standalone-extended-0.9.3.diff)
+* [st-scrollback-reflow-standalone-extended-0.9.31.diff](st-scrollback-reflow-standalone-extended-0.9.31.diff)
 
 Author
 ------
diff --git a/st.suckless.org/patches/scrollback-reflow-standalone/st-scrollback-reflow-standalone-extended-0.9.3.diff b/st.suckless.org/patches/scrollback-reflow-standalone/st-scrollback-reflow-standalone-extended-0.9.31.diff
similarity index 87%
rename from st.suckless.org/patches/scrollback-reflow-standalone/st-scrollback-reflow-standalone-extended-0.9.3.diff
rename to st.suckless.org/patches/scrollback-reflow-standalone/st-scrollback-reflow-standalone-extended-0.9.31.diff
index 1da54de3..dff4b710 100644
--- a/st.suckless.org/patches/scrollback-reflow-standalone/st-scrollback-reflow-standalone-extended-0.9.3.diff
+++ b/st.suckless.org/patches/scrollback-reflow-standalone/st-scrollback-reflow-standalone-extended-0.9.31.diff
_AT_@ -1,45 +1,35 @@
-From 55e224cf4d767db7d9184e70a0f3838935679a53 Mon Sep 17 00:00:00 2001
+From 792cbb832839cb6981440356c26ce2836bc69427 Mon Sep 17 00:00:00 2001
 From: Milos Nikic <nikic.milos_AT_gmail.com>
-Date: Thu, 15 Jan 2026 16:08:59 -0800
+Date: Thu, 8 Jan 2026 22:04:25 -0800
 Subject: [PATCH] st: alternative scrollback using ring buffer and view offset
 
 Implement scrollback as a fixed-size ring buffer and render history
 by offsetting the view instead of copying screen contents.
-Implement reflow of history and screen content on resize if it is needed.
 
 Tradeoffs / differences:
- - Scrollback is disabled on the alternate screen
- - Simpler model than the existing scrollback patch set
- - Mouse wheel scrolling enabled by default
- - Shift + page up/down and shift + end/home work as well.
- - When using vim, mouse movement will no longer move the cursor.
- - There can be visual artifacts if width of the window is shrank to the
- size smaller than the shell promp.
- - Mouse selection is persistent even if it goes off screen but it get
- reset on resize.
+- Scrollback history is lost on resize
+- Scrollback is disabled on the alternate screen
+- Simpler model than the existing scrollback patch set
+- Mouse wheel scrolling enabled by default
+
+Note:
+When using vim, mouse movement will no longer move the cursor.
+
+Reminder:
+If applying this patch on top of others, ensure any changes to
+config.def.h are merged into config.h.
 ---
- config.def.h | 9 +
- st.c | 727 ++++++++++++++++++++++++++++++++++++++++++++-------
+ config.def.h | 5 +
+ st.c | 713 ++++++++++++++++++++++++++++++++++++++++++++-------
  st.h | 5 +
  x.c | 17 ++
- 4 files changed, 659 insertions(+), 99 deletions(-)
+ 4 files changed, 645 insertions(+), 95 deletions(-)
 
 diff --git a/config.def.h b/config.def.h
-index 2cd740a..135a0b1 100644
+index 2cd740a..a0b14e9 100644
 --- a/config.def.h
 +++ b/config.def.h
-_AT_@ -192,6 +192,10 @@ static Shortcut shortcuts[] = {
- { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} },
- { ControlMask, XK_Print, toggleprinter, {.i = 0} },
- { ShiftMask, XK_Print, printscreen, {.i = 0} },
-+ { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} },
-+ { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} },
-+ { ShiftMask, XK_Home, kscrollup, {.i = 1000000} },
-+ { ShiftMask, XK_End, kscrolldown, {.i = 1000000} },
- { XK_ANY_MOD, XK_Print, printsel, {.i = 0} },
- { TERMMOD, XK_Prior, zoom, {.f = +1} },
- { TERMMOD, XK_Next, zoom, {.f = -1} },
-_AT_@ -472,3 +476,8 @@ static char ascii_printable[] =
+_AT_@ -472,3 +472,8 @@ static char ascii_printable[] =
          " !\"#$%&'()*+,-./0123456789:;<=>?"
          "_AT_ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_"
          "`abcdefghijklmnopqrstuvwxyz{|}~";
_AT_@ -49,7 +39,7 @@ index 2cd740a..135a0b1 100644
 + */
 +unsigned int scrollback_lines = 5000;
 diff --git a/st.c b/st.c
-index e55e7b3..9565003 100644
+index 6f40e35..cf76c58 100644
 --- a/st.c
 +++ b/st.c
 _AT_@ -5,6 +5,7 @@
_AT_@ -69,7 +59,7 @@ index e55e7b3..9565003 100644
  static void tmoveto(int, int);
  static void tmoveato(int, int);
  static void tnewline(int);
-_AT_@ -232,6 +233,376 @@ static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
+_AT_@ -232,6 +233,379 @@ static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
  static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
  static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
  
_AT_@ -379,6 +369,9 @@ index e55e7b3..9565003 100644
 + sel.ob.y += delta;
 + sel.oe.y += delta;
 +
++ if (sel.ne.y < 0 || sel.nb.y >= term.row)
++ selclear();
++
 + sb_view_changed();
 +}
 +
_AT_@ -446,7 +439,7 @@ index e55e7b3..9565003 100644
  ssize_t
  xwrite(int fd, const char *s, size_t len)
  {
-_AT_@ -404,20 +775,23 @@ selinit(void)
+_AT_@ -404,20 +778,23 @@ selinit(void)
          sel.ob.x = -1;
  }
  
_AT_@ -477,7 +470,7 @@ index e55e7b3..9565003 100644
  void
  selstart(int col, int row, int snap)
  {
-_AT_@ -485,10 +859,10 @@ selnormalize(void)
+_AT_@ -485,10 +862,10 @@ selnormalize(void)
          /* expand selection over line breaks */
          if (sel.type == SEL_RECTANGULAR)
                  return;
_AT_@ -490,7 +483,7 @@ index e55e7b3..9565003 100644
                  sel.ne.x = term.col - 1;
  }
  
-_AT_@ -514,6 +888,7 @@ selsnap(int *x, int *y, int direction)
+_AT_@ -514,6 +891,7 @@ selsnap(int *x, int *y, int direction)
          int newx, newy, xt, yt;
          int delim, prevdelim;
          const Glyph *gp, *prevgp;
_AT_@ -498,7 +491,7 @@ index e55e7b3..9565003 100644
  
          switch (sel.snap) {
          case SNAP_WORD:
-_AT_@ -521,7 +896,7 @@ selsnap(int *x, int *y, int direction)
+_AT_@ -521,7 +899,7 @@ selsnap(int *x, int *y, int direction)
                   * Snap around if the word wraps around at the end or
                   * beginning of a line.
                   */
_AT_@ -507,7 +500,7 @@ index e55e7b3..9565003 100644
                  prevdelim = ISDELIM(prevgp->u);
                  for (;;) {
                          newx = *x + direction;
-_AT_@ -536,14 +911,15 @@ selsnap(int *x, int *y, int direction)
+_AT_@ -536,14 +914,15 @@ selsnap(int *x, int *y, int direction)
                                          yt = *y, xt = *x;
                                  else
                                          yt = newy, xt = newx;
_AT_@ -526,7 +519,7 @@ index e55e7b3..9565003 100644
                          delim = ISDELIM(gp->u);
                          if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
                                          || (delim && gp->u != prevgp->u)))
-_AT_@ -564,14 +940,14 @@ selsnap(int *x, int *y, int direction)
+_AT_@ -564,14 +943,14 @@ selsnap(int *x, int *y, int direction)
                  *x = (direction < 0) ? 0 : term.col - 1;
                  if (direction < 0) {
                          for (; *y > 0; *y += direction) {
_AT_@ -543,7 +536,7 @@ index e55e7b3..9565003 100644
                                                  & ATTR_WRAP)) {
                                          break;
                                  }
-_AT_@ -585,8 +961,9 @@ char *
+_AT_@ -585,8 +964,9 @@ char *
  getsel(void)
  {
          char *str, *ptr;
_AT_@ -554,7 +547,7 @@ index e55e7b3..9565003 100644
  
          if (sel.ob.x == -1)
                  return NULL;
-_AT_@ -596,29 +973,33 @@ getsel(void)
+_AT_@ -596,29 +976,33 @@ getsel(void)
  
          /* append every set & selected glyph to the selection */
          for (y = sel.nb.y; y <= sel.ne.y; y++) {
_AT_@ -595,7 +588,7 @@ index e55e7b3..9565003 100644
                  /*
                   * Copy and pasting of line endings is inconsistent
                   * in the inconsistent terminal and GUI world.
-_AT_@ -628,8 +1009,13 @@ getsel(void)
+_AT_@ -628,8 +1012,13 @@ getsel(void)
                   * st.
                   * FIXME: Fix the computer world.
                   */
_AT_@ -610,7 +603,7 @@ index e55e7b3..9565003 100644
                          *ptr++ = '
';
          }
          *ptr = 0;
-_AT_@ -845,6 +1231,12 @@ ttywrite(const char *s, size_t n, int may_echo)
+_AT_@ -845,6 +1234,12 @@ ttywrite(const char *s, size_t n, int may_echo)
  {
          const char *next;
  
_AT_@ -623,16 +616,18 @@ index e55e7b3..9565003 100644
          if (may_echo && IS_SET(MODE_ECHO))
                  twrite(s, n, 1);
  
-_AT_@ -965,6 +1357,8 @@ tsetdirt(int top, int bot)
+_AT_@ -965,9 +1360,8 @@ tsetdirt(int top, int bot)
  {
          int i;
  
+- if (term.row <= 0)
 + if (term.row < 1)
-+ return;
+ return;
+-
          LIMIT(top, 0, term.row-1);
          LIMIT(bot, 0, term.row-1);
  
-_AT_@ -1030,15 +1424,21 @@ treset(void)
+_AT_@ -1033,15 +1427,21 @@ treset(void)
          for (i = 0; i < 2; i++) {
                  tmoveto(0, 0);
                  tcursor(CURSOR_SAVE);
_AT_@ -655,7 +650,7 @@ index e55e7b3..9565003 100644
          tresize(col, row);
          treset();
  }
-_AT_@ -1078,10 +1478,37 @@ void
+_AT_@ -1081,10 +1481,37 @@ void
  tscrollup(int orig, int n)
  {
          int i;
_AT_@ -693,7 +688,7 @@ index e55e7b3..9565003 100644
          tclearregion(0, orig, term.col-1, orig+n-1);
          tsetdirt(orig+n, term.bot);
  
-_AT_@ -1097,6 +1524,8 @@ tscrollup(int orig, int n)
+_AT_@ -1100,6 +1527,8 @@ tscrollup(int orig, int n)
  void
  selscroll(int orig, int n)
  {
_AT_@ -702,21 +697,7 @@ index e55e7b3..9565003 100644
          if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
                  return;
  
-_AT_@ -1105,12 +1534,7 @@ selscroll(int orig, int n)
- } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
- sel.ob.y += n;
- sel.oe.y += n;
-- if (sel.ob.y < term.top || sel.ob.y > term.bot ||
-- sel.oe.y < term.top || sel.oe.y > term.bot) {
-- selclear();
-- } else {
-- selnormalize();
-- }
-+ selnormalize();
- }
- }
-
-_AT_@ -1717,6 +2141,12 @@ csihandle(void)
+_AT_@ -1720,6 +2149,12 @@ csihandle(void)
                          break;
                  case 2: /* all */
                          tclearregion(0, 0, term.col-1, term.row-1);
_AT_@ -729,7 +710,7 @@ index e55e7b3..9565003 100644
                          break;
                  default:
                          goto unknown;
-_AT_@ -2106,7 +2536,7 @@ tdumpline(int n)
+_AT_@ -2109,7 +2544,7 @@ tdumpline(int n)
          const Glyph *bp, *end;
  
          bp = &term.line[n][0];
_AT_@ -738,7 +719,7 @@ index e55e7b3..9565003 100644
          if (bp != end || bp->u != ' ') {
                  for ( ; bp <= end; ++bp)
                          tprinter(buf, utf8encode(bp->u, buf));
-_AT_@ -2163,6 +2593,46 @@ tdeftran(char ascii)
+_AT_@ -2166,6 +2601,36 @@ tdeftran(char ascii)
          }
  }
  
_AT_@ -752,6 +733,7 @@ index e55e7b3..9565003 100644
 + sb.view_offset += arg->i;
 + LIMIT(sb.view_offset, 0, sb.len);
 + newstart = sb_view_start();
++
 + selscrollback(oldstart - newstart);
 + redraw();
 +}
_AT_@ -761,31 +743,20 @@ index e55e7b3..9565003 100644
 +{
 + Arg a;
 +
-+ if (arg->i < 0)
-+ a.i = -term.row;
-+ else
-+ a.i = -arg->i;
-+
++ a.i = -arg->i;
 + kscroll(&a);
 +}
 +
 +void
 +kscrollup(const Arg *arg)
 +{
-+ Arg a;
-+
-+ if (arg->i < 0)
-+ a.i = term.row;
-+ else
-+ a.i = arg->i;
-+
-+ kscroll(&a);
++ kscroll(arg);
 +}
 +
  void
  tdectest(char c)
  {
-_AT_@ -2569,83 +3039,139 @@ twrite(const char *buf, int buflen, int show_ctrl)
+_AT_@ -2572,83 +3037,138 @@ twrite(const char *buf, int buflen, int show_ctrl)
  void
  tresize(int col, int row)
  {
_AT_@ -829,9 +800,6 @@ index e55e7b3..9565003 100644
 - for (i += row; i < term.row; i++) {
 - free(term.line[i]);
 - free(term.alt[i]);
-+ if (sel.ob.x != -1)
-+ selclear();
-+
 + /* Operate on the currently visible screen buffer. */
 + if (is_alt) {
 + tmp = term.line;
_AT_@ -873,8 +841,10 @@ index e55e7b3..9565003 100644
 + * This avoids expensive reflow operations when resizing doesn't
 + * affect line wrapping (e.g., when terminal is wide enough). */
 + if (col > term.col) {
-+ /* Growing: Only reflow if history was wrapped at old width */
-+ needs_reflow = sb.max_width >= term.col;
++ /* Growing: We MUST reflow. Even if the text doesn't need
++ * un-wrapping, the history lines must be physically reallocated
++ * to the new width to prevent heap-buffer-overflows on read. */
++ needs_reflow = 1;
 + } else if (col < term.col) {
 + /* Shrinking: Only reflow if content is wider than new width. */
 + if (sb.max_width > col)
_AT_@ -883,7 +853,7 @@ index e55e7b3..9565003 100644
 + if (needs_reflow) {
 + sb_resize(col);
 + } else {
-+ /* If we don't reflow, we still need to reset the view
++ /* If we don't reflow, we still need to reset the view
 + * because sb_pop_screen() might change the history length. */
 + sb.view_offset = 0;
 + }
_AT_@ -985,7 +955,7 @@ index e55e7b3..9565003 100644
  }
  
  void
-_AT_@ -2659,12 +3185,13 @@ drawregion(int x1, int y1, int x2, int y2)
+_AT_@ -2662,12 +3182,13 @@ drawregion(int x1, int y1, int x2, int y2)
  {
          int y;
  
_AT_@ -1001,7 +971,7 @@ index e55e7b3..9565003 100644
          }
  }
  
-_AT_@ -2685,10 +3212,12 @@ draw(void)
+_AT_@ -2688,10 +3209,12 @@ draw(void)
                  cx--;
  
          drawregion(0, 0, term.col, term.row);
_AT_@ -1074,5 +1044,5 @@ index d73152b..75f3db1 100644
                  buttons |= 1 << (btn-1);
  
 --
-2.52.0
+2.53.0
 
Received on Mon Feb 23 2026 - 19:40:53 CET

This archive was generated by hypermail 2.3.0 : Mon Feb 23 2026 - 19:48:48 CET