[wiki] [sites] [ST][PATCH] Bug fix in scrollback reflow standalone extended. || Milos Nikic
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