commit 24e1c6729e3c24b178b16fccba5dcb52b6e99388
parent 5ed94a02a3338548633c8d745bf14fe38d3e3ffb
Author: Milos Nikic <nikic.milos@gmail.com>
Date: Sun, 18 Oct 2026 18:03:13 -0700
[ST][PATCH] Another bug fix in scrollback reflow standalone extended.
Diffstat:
1 file changed, 381 insertions(+), 277 deletions(-)
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.3.diff
@@ -1,6 +1,6 @@
-From 5fbf33797d8d286b94a69babbfee014f58dfe87e Mon Sep 17 00:00:00 2001
+From c7cf4aae956ea0db9253b6592749c578d1546ce5 Mon Sep 17 00:00:00 2001
From: Milos Nikic <nikic.milos@gmail.com>
-Date: Sun, 18 Oct 2026 11:58:07 -0700
+Date: Thu, 8 Jan 2026 22:04:25 -0800
Subject: [PATCH] st: scrollback reflow and selection across scrollback
Reflow the scrollback buffer on resize instead of dropping it: wrapped
@@ -21,19 +21,39 @@ ring shifts it.
- typing while scrolled back no longer clears the selection.
- the selection is cleared when the width changes (reflow) or the alt
screen is resized, and kept on height-only resizes.
-
-Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---
- st.c | 707 +++++++++++++++++++++++++++++++++++++++++++----------------
- st.h | 1 +
- x.c | 29 ++-
- 3 files changed, 537 insertions(+), 200 deletions(-)
+ config.def.h | 5 +
+ st.c | 840 ++++++++++++++++++++++++++++++++++++++++++++-------
+ st.h | 6 +
+ x.c | 46 ++-
+ 4 files changed, 780 insertions(+), 117 deletions(-)
+diff --git a/config.def.h b/config.def.h
+index fd2bd87..0e885f1 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -472,3 +472,8 @@ static char ascii_printable[] =
+ " !\"#$%&'()*+,-./0123456789:;<=>?"
+ "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
+ "`abcdefghijklmnopqrstuvwxyz{|}~";
++
++/*
++ * The amount of lines scrollback can hold before it wraps around.
++ */
++unsigned int scrollback_lines = 5000;
diff --git a/st.c b/st.c
-index dc30082..4d79f20 100644
+index c289072..4d79f20 100644
--- a/st.c
+++ b/st.c
-@@ -179,7 +179,7 @@ static void tdeletechar(int);
+@@ -5,6 +5,7 @@
+ #include <limits.h>
+ #include <pwd.h>
+ #include <stdarg.h>
++#include <stdint.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+@@ -178,7 +179,7 @@ static void tdeletechar(int);
static void tdeleteline(int);
static void tinsertblank(int);
static void tinsertblankline(int);
@@ -42,7 +62,7 @@ index dc30082..4d79f20 100644
static void tmoveto(int, int);
static void tmoveato(int, int);
static void tnewline(int);
-@@ -208,6 +208,9 @@ static void drawregion(int, int, int, int);
+@@ -207,6 +208,9 @@ static void drawregion(int, int, int, int);
static void selnormalize(void);
static void selscroll(int, int);
static void selsnap(int *, int *, int);
@@ -52,16 +72,10 @@ index dc30082..4d79f20 100644
static size_t utf8decode(const char *, Rune *, size_t);
static Rune utf8decodebyte(char, size_t *);
-@@ -233,16 +236,25 @@ static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
+@@ -232,6 +236,366 @@ 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};
--typedef struct {
-- Line *buf; /* ring of Line pointers */
-- int cap; /* max number of lines */
-- int len; /* current number of valid lines (<= cap) */
-- int head; /* physical index of logical oldest (valid when len>0) */
-- uint64_t base;
+typedef struct
+{
+ Line *buf; /* ring of Line pointers */
@@ -78,80 +92,113 @@ index dc30082..4d79f20 100644
+ */
+ int max_width;
+ int view_offset; /* 0 means live screen */
- } Scrollback;
-
- static Scrollback sb;
--static int view_offset;
-
- static int
- sb_phys_index(int logical_idx)
-@@ -254,7 +266,12 @@ sb_phys_index(int logical_idx)
- static Line
- lineclone(Line src)
- {
-- Line dst = xmalloc(term.col * sizeof(Glyph));
++} Scrollback;
++
++static Scrollback sb;
++
++static int
++sb_phys_index(int logical_idx)
++{
++ /* logical_idx: 0..sb.len-1 (0 = oldest) */
++ return (sb.head + logical_idx) % sb.cap;
++}
++
++static Line
++lineclone(Line src)
++{
+ Line dst;
+
+ if (!src)
+ return NULL;
+
+ dst = xmalloc(term.col * sizeof(Glyph));
- memcpy(dst, src, term.col * sizeof(Glyph));
- return dst;
- }
-@@ -272,7 +289,8 @@ sb_init(int lines)
- for (i = 0; i < sb.cap; i++)
- sb.buf[i] = NULL;
-
-- view_offset = 0;
++ memcpy(dst, src, term.col * sizeof(Glyph));
++ return dst;
++}
++
++static void
++sb_init(int lines)
++{
++ int i;
++
++ sb.buf = xmalloc(sizeof(Line) * lines);
++ sb.cap = lines;
++ sb.len = 0;
++ sb.head = 0;
++ sb.base = 0;
++ for (i = 0; i < sb.cap; i++)
++ sb.buf[i] = NULL;
++
+ sb.view_offset = 0;
+ sb.max_width = 0;
- }
-
- /* Push one screen line into scrollback.
-@@ -283,6 +301,7 @@ sb_push(Line line)
- {
- Line copy;
- int tail;
++}
++
++/* Push one screen line into scrollback.
++ * Overwrites oldest when full (ring buffer).
++ */
++static void
++sb_push(Line line)
++{
++ Line copy;
++ int tail;
+ int width;
-
- if (sb.cap <= 0)
- return;
-@@ -294,11 +313,17 @@ sb_push(Line line)
- sb.buf[tail] = copy;
- sb.len++;
- } else {
++
++ if (sb.cap <= 0)
++ return;
++
++ copy = lineclone(line);
++
++ if (sb.len < sb.cap) {
++ tail = sb_phys_index(sb.len);
++ sb.buf[tail] = copy;
++ sb.len++;
++ } else {
+ /* We might've just evicted the widest line... */
- free(sb.buf[sb.head]);
- sb.buf[sb.head] = copy;
- sb.head = (sb.head + 1) % sb.cap;
- sb.base++;
++ free(sb.buf[sb.head]);
++ sb.buf[sb.head] = copy;
++ sb.head = (sb.head + 1) % sb.cap;
++ sb.base++;
+ selevict();
- }
++ }
+ width = tlinelen(copy);
+ /* ...so max_width might be stale. */
+ if (width > sb.max_width)
+ sb.max_width = width;
- }
-
- static Line
-@@ -330,47 +355,169 @@ sb_clear(void)
- sb.len = 0;
- sb.head = 0;
- sb.base = 0;
-- view_offset = 0;
-- if (sel.ob.x != -1 && term.row > 0)
-- selclear();
--}
--
--static uint64_t
--sb_view_start(void)
--{
-- return sb.base + sb.len - view_offset;
++}
++
++static Line
++sb_get(int idx)
++{
++ /* idx is logical: 0..sb.len-1 */
++ if (idx < 0 || idx >= sb.len)
++ return NULL;
++ return sb.buf[sb_phys_index(idx)];
++}
++
++static void
++sb_clear(void)
++{
++ int i;
++ int p;
++
++ if (!sb.buf)
++ return;
++
++ for (i = 0; i < sb.len; i++) {
++ p = sb_phys_index(i);
++ if (sb.buf[p]) {
++ free(sb.buf[p]);
++ sb.buf[p] = NULL;
++ }
++ }
++
++ sb.len = 0;
++ sb.head = 0;
++ sb.base = 0;
+ sb.view_offset = 0;
+ sb.max_width = 0;
- }
-
++}
++
+/*
+ * Reflows the scrollback buffer to fit a new terminal width.
+ *
@@ -169,11 +216,9 @@ index dc30082..4d79f20 100644
+ * Note: During reflow we reset sb to match the rebuilt buffer
+ * (head, base and len might change).
+ */
- static void
--sb_view_changed(void)
++static void
+sb_resize(int col)
- {
-- if (!term.dirty || term.row <= 0)
++{
+ Line *new_buf;
+ int i, j;
+ int new_len, logical_cap, logical_len, is_wrapped, cursor;
@@ -187,8 +232,7 @@ index dc30082..4d79f20 100644
+ new_len = 0;
+
+ if (sb.len == 0)
- return;
-- tfulldirt();
++ return;
+
+ new_buf = xmalloc(sizeof(Line) * sb.cap);
+ for (i = 0; i < sb.cap; i++)
@@ -283,59 +327,60 @@ index dc30082..4d79f20 100644
+ sb.base = new_base;
+ sb.view_offset = 0;
+ sb.max_width = new_max_width;
- }
-
- static void
--selscrollback(int delta)
++}
++
++static void
+sb_pop_screen(int loaded, int new_cols)
- {
-- if (delta == 0)
-- return;
++{
+ int i, p;
+ int start_logical;
+ Line line;
-
-- if (sel.ob.x == -1)
-- return;
++
+ loaded = MIN(loaded, sb.len);
+ start_logical = sb.len - loaded;
+ new_cols = MIN(new_cols, term.col);
+ for (i = 0; i < loaded; i++) {
+ p = sb_phys_index(start_logical + i);
+ line = sb.buf[p];
-
-- if (sel.alt != IS_SET(MODE_ALTSCREEN))
-- return;
++
+ memcpy(term.line[i], line, new_cols * sizeof(Glyph));
-
-- sel.nb.y += delta;
-- sel.ne.y += delta;
-- sel.ob.y += delta;
-- sel.oe.y += delta;
++
+ free(line);
+ sb.buf[p] = NULL;
+ }
-
-- /* the selection is in window coordinates: drop it once it leaves */
-- if (sel.nb.y < 0 || sel.ne.y >= term.row)
-- selclear();
++
+ sb.len -= loaded;
+}
-
-- sb_view_changed();
++
+static void
+sb_view_changed(void)
+{
+ if (!term.dirty || term.row <= 0)
+ return;
+ tfulldirt();
- }
-
- static Line
-@@ -394,14 +541,16 @@ emptyline(void)
- return empty;
- }
-
++}
++
++static Line
++emptyline(void)
++{
++ static Line empty;
++ static int empty_cols;
++ int i = 0;
++
++ if (empty_cols != term.col) {
++ free(empty);
++ empty = xmalloc(term.col * sizeof(Glyph));
++ empty_cols = term.col;
++ }
++
++ for (i = 0; i < term.col; i++) {
++ empty[i] = term.c.attr;
++ empty[i].u = ' ';
++ empty[i].mode = 0;
++ }
++ return empty;
++}
++
+/*
+ * Logical rows address history and screen as one continuous space:
+ * 0..sb.len-1 are scrollback lines (0 = oldest) and sb.len + y is
@@ -343,29 +388,23 @@ index dc30082..4d79f20 100644
+ * scrollback; only evicting the oldest line shifts everything by one.
+ * The selection lives in this space, so it survives scrolling.
+ */
- static Line
--getlineforrender(int y)
++static Line
+logicalline(int v)
- {
-- int start, v;
--
-- start = sb.len - view_offset; /* can be negative */
-- v = start + y;
--
- if (v < 0)
- return emptyline();
-
-@@ -410,17 +559,35 @@ getlineforrender(int y)
-
- /* past scrollback -> into current screen */
- v -= sb.len;
-- if (v >= 0 && v < term.row)
++{
++ if (v < 0)
++ return emptyline();
++
++ if (v < sb.len)
++ return sb_get(v);
++
++ /* past scrollback -> into current screen */
++ v -= sb.len;
+ if (v < term.row)
- return term.line[v];
-
- return emptyline();
- }
-
++ return term.line[v];
++
++ return emptyline();
++}
++
+/* first logical row shown at the top of the window */
+static int
+viewtop(void)
@@ -382,17 +421,25 @@ index dc30082..4d79f20 100644
+ return logicalline(viewtop() + y);
+}
+
- static void
- sb_reset_on_clear(void)
- {
- sb_clear();
- sb_view_changed();
++static void
++sb_reset_on_clear(void)
++{
++ sb_clear();
++ sb_view_changed();
+ if (sel.ob.x != -1 && term.row > 0)
+ selclear();
- }
-
- int
-@@ -601,39 +768,66 @@ selinit(void)
++}
++
++int
++tisaltscreen(void)
++{
++ return IS_SET(MODE_ALTSCREEN);
++}
++
+ ssize_t
+ xwrite(int fd, const char *s, size_t len)
+ {
+@@ -404,20 +768,68 @@ selinit(void)
sel.ob.x = -1;
}
@@ -415,51 +462,39 @@ index dc30082..4d79f20 100644
}
+/* range of logical rows a selection can cover */
- static int
--tlinelen_render(int y)
++static int
+selminrow(void)
- {
-- int i;
-- Line line;
++{
+ /* the alternate screen has no history */
+ return sel.alt ? sb.len : 0;
+}
-
-- if (term.col <= 0)
-- return 0;
++
+static int
+selmaxrow(void)
+{
+ return sb.len + term.row - 1;
+}
-
-- i = term.col;
-- line = getlineforrender(y);
++
+/* mark the visible part of logical rows a..b dirty */
+static void
+seldirt(int a, int b)
+{
+ int top = viewtop();
-
-- if (line[i - 1].mode & ATTR_WRAP)
-- return i;
++
+ a -= top;
+ b -= top;
+ if (b < 0 || a >= term.row)
+ return;
+ tsetdirt(MAX(a, 0), MIN(b, term.row - 1));
+}
-
-- while (i > 0 && line[i - 1].u == ' ')
-- --i;
++
+/* the oldest scrollback line was dropped: shift the selection with it */
+static void
+selevict(void)
+{
+ if (sel.ob.x == -1 || sel.alt)
+ return;
-
-- return i;
++
+ sel.ob.y--;
+ sel.oe.y--;
+ sel.nb.y--;
@@ -475,10 +510,12 @@ index dc30082..4d79f20 100644
+ sel.ob.x = sel.ob.y = 0;
+ if (sel.oe.y < 0)
+ sel.oe.x = sel.oe.y = 0;
- }
-
++}
++
void
-@@ -645,12 +839,12 @@ selstart(int col, int row, int snap)
+ selstart(int col, int row, int snap)
+ {
+@@ -427,12 +839,12 @@ selstart(int col, int row, int snap)
sel.alt = IS_SET(MODE_ALTSCREEN);
sel.snap = snap;
sel.oe.x = sel.ob.x = col;
@@ -493,7 +530,7 @@ index dc30082..4d79f20 100644
}
void
-@@ -672,16 +866,48 @@ selextend(int col, int row, int type, int done)
+@@ -454,16 +866,48 @@ selextend(int col, int row, int type, int done)
oldtype = sel.type;
sel.oe.x = col;
@@ -544,15 +581,15 @@ index dc30082..4d79f20 100644
void
selnormalize(void)
{
-@@ -703,15 +929,16 @@ selnormalize(void)
+@@ -485,15 +929,16 @@ selnormalize(void)
/* expand selection over line breaks */
if (sel.type == SEL_RECTANGULAR)
return;
-- i = tlinelen_render(sel.nb.y);
+- i = tlinelen(sel.nb.y);
+ i = tlinelen(logicalline(sel.nb.y));
if (i < sel.nb.x)
sel.nb.x = i;
-- if (tlinelen_render(sel.ne.y) <= sel.ne.x)
+- if (tlinelen(sel.ne.y) <= sel.ne.x)
+ if (tlinelen(logicalline(sel.ne.y)) <= sel.ne.x)
sel.ne.x = term.col - 1;
}
@@ -565,7 +602,7 @@ index dc30082..4d79f20 100644
{
if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
sel.alt != IS_SET(MODE_ALTSCREEN))
-@@ -726,11 +953,19 @@ selected(int x, int y)
+@@ -508,12 +953,21 @@ selected(int x, int y)
&& (y != sel.ne.y || x <= sel.ne.x);
}
@@ -583,18 +620,20 @@ index dc30082..4d79f20 100644
int delim, prevdelim;
+ int miny = selminrow(), maxy = selmaxrow();
const Glyph *gp, *prevgp;
- Line line;
++ Line line;
-@@ -740,7 +975,7 @@ selsnap(int *x, int *y, int direction)
+ switch (sel.snap) {
+ case SNAP_WORD:
+@@ -521,7 +975,7 @@ selsnap(int *x, int *y, int direction)
* Snap around if the word wraps around at the end or
* beginning of a line.
*/
-- prevgp = &getlineforrender(*y)[*x];
+- prevgp = &term.line[*y][*x];
+ prevgp = &logicalline(*y)[*x];
prevdelim = ISDELIM(prevgp->u);
for (;;) {
newx = *x + direction;
-@@ -748,22 +983,23 @@ selsnap(int *x, int *y, int direction)
+@@ -529,21 +983,23 @@ selsnap(int *x, int *y, int direction)
if (!BETWEEN(newx, 0, term.col - 1)) {
newy += direction;
newx = (newx + term.col) % term.col;
@@ -606,28 +645,28 @@ index dc30082..4d79f20 100644
yt = *y, xt = *x;
else
yt = newy, xt = newx;
-- line = getlineforrender(yt);
+- if (!(term.line[yt][xt].mode & ATTR_WRAP))
+ line = logicalline(yt);
- if (!(line[xt].mode & ATTR_WRAP))
++ if (!(line[xt].mode & ATTR_WRAP))
break;
}
-- if (newx >= tlinelen_render(newy))
+- if (newx >= tlinelen(newy))
+ line = logicalline(newy);
+ if (newx >= tlinelen(line))
break;
-- gp = &getlineforrender(newy)[newx];
+- gp = &term.line[newy][newx];
+ gp = &line[newx];
delim = ISDELIM(gp->u);
if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
|| (delim && gp->u != prevgp->u)))
-@@ -783,15 +1019,15 @@ selsnap(int *x, int *y, int direction)
+@@ -563,15 +1019,15 @@ selsnap(int *x, int *y, int direction)
*/
*x = (direction < 0) ? 0 : term.col - 1;
if (direction < 0) {
- for (; *y > 0; *y += direction) {
-- if (!(getlineforrender(*y-1)[term.col-1].mode
+- if (!(term.line[*y-1][term.col-1].mode
+ for (; *y > miny; *y += direction) {
+ if (!(logicalline(*y-1)[term.col-1].mode
& ATTR_WRAP)) {
@@ -636,13 +675,13 @@ index dc30082..4d79f20 100644
}
} else if (direction > 0) {
- for (; *y < term.row-1; *y += direction) {
-- if (!(getlineforrender(*y)[term.col-1].mode
+- if (!(term.line[*y][term.col-1].mode
+ for (; *y < maxy; *y += direction) {
+ if (!(logicalline(*y)[term.col-1].mode
& ATTR_WRAP)) {
break;
}
-@@ -805,8 +1041,9 @@ char *
+@@ -585,8 +1041,9 @@ char *
getsel(void)
{
char *str, *ptr;
@@ -653,12 +692,11 @@ index dc30082..4d79f20 100644
if (sel.ob.x == -1)
return NULL;
-@@ -816,8 +1053,10 @@ getsel(void)
+@@ -596,29 +1053,33 @@ getsel(void)
/* append every set & selected glyph to the selection */
for (y = sel.nb.y; y <= sel.ne.y; y++) {
-- Line line = getlineforrender(y);
-- if ((linelen = tlinelen_render(y)) == 0) {
+- if ((linelen = tlinelen(y)) == 0) {
+ line = logicalline(y);
+ linelen = tlinelen(line);
+
@@ -666,11 +704,17 @@ index dc30082..4d79f20 100644
*ptr++ = '\n';
continue;
}
-@@ -829,17 +1068,18 @@ getsel(void)
- gp = &line[sel.nb.y == y ? sel.nb.x : 0];
+
+ if (sel.type == SEL_RECTANGULAR) {
+- gp = &term.line[y][sel.nb.x];
++ gp = &line[sel.nb.x];
+ lastx = sel.ne.x;
+ } else {
+- gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
++ gp = &line[sel.nb.y == y ? sel.nb.x : 0];
lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
}
-- last = &line[MIN(lastx, linelen-1)];
+- last = &term.line[y][MIN(lastx, linelen-1)];
- while (last >= gp && last->u == ' ')
+ end_idx = MIN(lastx, linelen-1);
+ is_wrapped = (line[end_idx].mode & ATTR_WRAP) != 0;
@@ -689,7 +733,7 @@ index dc30082..4d79f20 100644
/*
* Copy and pasting of line endings is inconsistent
* in the inconsistent terminal and GUI world.
-@@ -849,8 +1089,13 @@ getsel(void)
+@@ -628,8 +1089,13 @@ getsel(void)
* st.
* FIXME: Fix the computer world.
*/
@@ -704,7 +748,7 @@ index dc30082..4d79f20 100644
*ptr++ = '\n';
}
*ptr = 0;
-@@ -864,7 +1109,7 @@ selclear(void)
+@@ -643,7 +1109,7 @@ selclear(void)
return;
sel.mode = SEL_IDLE;
sel.ob.x = -1;
@@ -713,26 +757,19 @@ index dc30082..4d79f20 100644
}
void
-@@ -1069,14 +1314,13 @@ ttyread(void)
- void
- ttywrite(const char *s, size_t n, int may_echo)
+@@ -850,6 +1316,11 @@ ttywrite(const char *s, size_t n, int may_echo)
{
-- if (view_offset > 0) {
-- selclear();
-- view_offset = 0;
-+ const char *next;
-+
+ const char *next;
+
+ if (sb.view_offset > 0) {
+ sb.view_offset = 0;
- sb_view_changed();
- }
-
-- const char *next;
--
++ sb_view_changed();
++ }
++
if (may_echo && IS_SET(MODE_ECHO))
twrite(s, n, 1);
-@@ -1197,9 +1441,8 @@ tsetdirt(int top, int bot)
+@@ -970,9 +1441,8 @@ tsetdirt(int top, int bot)
{
int i;
@@ -743,7 +780,7 @@ index dc30082..4d79f20 100644
LIMIT(top, 0, term.row-1);
LIMIT(bot, 0, term.row-1);
-@@ -1265,12 +1508,16 @@ treset(void)
+@@ -1038,15 +1508,21 @@ treset(void)
for (i = 0; i < 2; i++) {
tmoveto(0, 0);
tcursor(CURSOR_SAVE);
@@ -752,7 +789,7 @@ index dc30082..4d79f20 100644
+ tclearregion(0, 0, term.col-1, term.row-1);
tswapscreen();
}
- sb_clear();
++ sb_clear();
+ if (sel.ob.x != -1 && term.row > 0)
+ selclear();
}
@@ -761,22 +798,22 @@ index dc30082..4d79f20 100644
void
tnew(int col, int row)
{
-@@ -1315,35 +1562,53 @@ void
+ term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
++ sb_init(scrollback_lines);
+ tresize(col, row);
+ treset();
+ }
+@@ -1086,10 +1562,54 @@ void
tscrollup(int orig, int n)
{
int i;
-- uint64_t newstart;
-- uint64_t oldstart;
--
+ int attop;
+ int pushed = 0;
Line temp;
-- oldstart = sb_view_start();
LIMIT(n, 0, term.bot-orig+1);
- if (!IS_SET(MODE_ALTSCREEN) && orig == term.top) {
-- int attop;
++ if (!IS_SET(MODE_ALTSCREEN) && orig == term.top) {
+ /*
+ * Pushed lines keep their logical rows, but rows outside
+ * of a partial scroll region move along with the screen.
@@ -784,34 +821,25 @@ index dc30082..4d79f20 100644
+ if ((term.top > 0 || term.bot < term.row-1) &&
+ sel.ob.x != -1 && !sel.alt && sel.ne.y >= sb.len)
+ selclear();
-
- /* At top of history only if history exists */
-- attop = (sb.len != 0 && view_offset == sb.len);
++
++ /* At top of history only if history exists */
+ attop = (sb.len != 0 && sb.view_offset == sb.len);
-
-- if (view_offset > 0 && !attop)
-- view_offset += n;
++
+ if (sb.view_offset > 0 && !attop)
+ sb.view_offset += n;
-
- for (i = 0; i < n; i++)
- sb_push(term.line[orig + i]);
++
++ for (i = 0; i < n; i++)
++ sb_push(term.line[orig + i]);
+ pushed = sb.cap > 0;
-
++
+ /* if at the top, keep me there */
- if (attop)
-- view_offset = sb.len;
-- else if (view_offset > sb.len)
-- view_offset = sb.len;
++ if (attop)
+ sb.view_offset = sb.len;
+ /* otherwise clamp me */
+ else if (sb.view_offset > sb.len)
+ sb.view_offset = sb.len;
- }
-
-- newstart = sb_view_start();
-- if (view_offset > 0)
-- selscrollback(oldstart - newstart);
++ }
++
+ if (pushed) {
+ /*
+ * The lines went to history, so the selection stays valid.
@@ -826,15 +854,14 @@ index dc30082..4d79f20 100644
+ tclearregion(0, term.bot-n+1, term.col-1, term.bot);
+ return;
+ }
-
++
tclearregion(0, orig, term.col-1, orig+n-1);
tsetdirt(orig+n, term.bot);
-@@ -1360,18 +1625,23 @@ tscrollup(int orig, int n)
+
+@@ -1105,16 +1625,23 @@ tscrollup(int orig, int n)
void
selscroll(int orig, int n)
{
-- if (view_offset != 0)
-- return;
+ int top, bot;
+
if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
@@ -859,7 +886,7 @@ index dc30082..4d79f20 100644
selclear();
} else {
selnormalize();
-@@ -1507,7 +1777,7 @@ tclearregion(int x1, int y1, int x2, int y2)
+@@ -1250,7 +1777,7 @@ tclearregion(int x1, int y1, int x2, int y2)
term.dirty[y] = 1;
for (x = x1; x <= x2; x++) {
gp = &term.line[y][x];
@@ -868,7 +895,20 @@ index dc30082..4d79f20 100644
selclear();
gp->fg = term.c.attr.fg;
gp->bg = term.c.attr.bg;
-@@ -2379,7 +2649,7 @@ tdumpline(int n)
+@@ -1727,6 +2254,12 @@ csihandle(void)
+ break;
+ case 2: /* all */
+ tclearregion(0, 0, term.col-1, term.row-1);
++ if (!IS_SET(MODE_ALTSCREEN))
++ sb_reset_on_clear();
++ break;
++ case 3:
++ if (!IS_SET(MODE_ALTSCREEN))
++ sb_reset_on_clear();
+ break;
+ default:
+ goto unknown;
+@@ -2116,7 +2649,7 @@ tdumpline(int n)
const Glyph *bp, *end;
bp = &term.line[n][0];
@@ -877,25 +917,37 @@ index dc30082..4d79f20 100644
if (bp != end || bp->u != ' ') {
for ( ; bp <= end; ++bp)
tprinter(buf, utf8encode(bp->u, buf));
-@@ -2439,15 +2709,8 @@ tdeftran(char ascii)
- static void
- kscroll(const Arg *arg)
- {
-- uint64_t oldstart;
-- uint64_t newstart;
--
-- oldstart = sb_view_start();
-- view_offset += arg->i;
-- LIMIT(view_offset, 0, sb.len);
-- newstart = sb_view_start();
--
-- selscrollback(oldstart - newstart);
-+ sb.view_offset += arg->i;
-+ LIMIT(sb.view_offset, 0, sb.len);
- redraw();
+@@ -2173,6 +2706,29 @@ tdeftran(char ascii)
+ }
}
-@@ -2793,7 +3056,7 @@ check_control_code:
++static void
++kscroll(const Arg *arg)
++{
++ sb.view_offset += arg->i;
++ LIMIT(sb.view_offset, 0, sb.len);
++ redraw();
++}
++
++void
++kscrolldown(const Arg *arg)
++{
++ Arg a;
++
++ a.i = -arg->i;
++ kscroll(&a);
++}
++
++void
++kscrollup(const Arg *arg)
++{
++ kscroll(arg);
++}
++
+ void
+ tdectest(char c)
+ {
+@@ -2500,7 +3056,7 @@ check_control_code:
*/
return;
}
@@ -904,7 +956,7 @@ index dc30082..4d79f20 100644
selclear();
gp = &term.line[term.c.y][term.c.x];
-@@ -2872,86 +3135,146 @@ twrite(const char *buf, int buflen, int show_ctrl)
+@@ -2579,83 +3135,146 @@ twrite(const char *buf, int buflen, int show_ctrl)
void
tresize(int col, int row)
{
@@ -915,9 +967,6 @@ index dc30082..4d79f20 100644
- int mincol = MIN(col, term.col);
- int *bp;
- TCursor c;
--
-- sb_clear();
-- sb_view_changed();
+ int old_row = term.row;
+ int old_col = term.col;
+ int save_end = 0; /* Track effective pushed height */
@@ -1112,29 +1161,52 @@ index dc30082..4d79f20 100644
}
void
-@@ -2970,7 +3293,7 @@ drawregion(int x1, int y1, int x2, int y2)
+@@ -2669,12 +3288,13 @@ drawregion(int x1, int y1, int x2, int y2)
+ {
+ int y;
+
++ Line line;
+ for (y = y1; y < y2; y++) {
if (!term.dirty[y])
continue;
+-
term.dirty[y] = 0;
-- line = getlineforrender(y);
+- xdrawline(term.line[y], x1, y, x2);
+ line = renderline(y);
- xdrawline(line, x1, y, x2);
++ xdrawline(line, x1, y, x2);
}
}
-@@ -2992,7 +3315,7 @@ draw(void)
+
+@@ -2695,10 +3315,12 @@ draw(void)
cx--;
drawregion(0, 0, term.col, term.row);
-- if (view_offset == 0) {
+- xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
+- term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
+- term.ocx = cx;
+- term.ocy = term.c.y;
+ if (sb.view_offset == 0) {
- xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
- term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
- term.ocx = cx;
++ xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
++ term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
++ term.ocx = cx;
++ term.ocy = term.c.y;
++ }
+ xfinishdraw();
+ if (ocx != term.ocx || ocy != term.ocy)
+ xximspot(term.ocx, term.ocy);
diff --git a/st.h b/st.h
-index 151d0c6..66f1436 100644
+index fd3b0d8..66f1436 100644
--- a/st.h
+++ b/st.h
-@@ -103,6 +103,7 @@ void selclear(void);
+@@ -86,6 +86,7 @@ void printsel(const Arg *);
+ void sendbreak(const Arg *);
+ void toggleprinter(const Arg *);
+
++int tisaltscreen(void);
+ int tattrset(int);
+ void tnew(int, int);
+ void tresize(int, int);
+@@ -102,6 +103,7 @@ void selclear(void);
void selinit(void);
void selstart(int, int, int);
void selextend(int, int, int, int);
@@ -1142,8 +1214,23 @@ index 151d0c6..66f1436 100644
int selected(int, int);
char *getsel(void);
+@@ -111,6 +113,9 @@ void *xmalloc(size_t);
+ void *xrealloc(void *, size_t);
+ char *xstrdup(const char *);
+
++void kscrollup(const Arg *arg);
++void kscrolldown(const Arg *arg);
++
+ /* config.h globals */
+ extern char *utmp;
+ extern char *scroll;
+@@ -124,3 +129,4 @@ extern unsigned int tabspaces;
+ extern unsigned int defaultfg;
+ extern unsigned int defaultbg;
+ extern unsigned int defaultcs;
++extern unsigned int scrollback_lines;
diff --git a/x.c b/x.c
-index dfe416a..8091e6f 100644
+index 76f572d..8091e6f 100644
--- a/x.c
+++ b/x.c
@@ -181,6 +181,7 @@ static void selnotify(XEvent *);
@@ -1186,17 +1273,34 @@ index dfe416a..8091e6f 100644
if (done)
setsel(getsel(), e->xbutton.time);
}
-@@ -485,6 +490,9 @@ bpress(XEvent *e)
- } else {
- kscrolldown(&a);
- }
+@@ -472,6 +477,26 @@ bpress(XEvent *e)
+ struct timespec now;
+ int snap;
+
++ if (btn == Button4 || btn == Button5) {
++ Arg a;
++ if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
++ mousereport(e);
++ return;
++ }
++ if (!tisaltscreen()) {
++ a.i = 1;
++ if (btn == Button4) {
++ kscrollup(&a);
++ } else {
++ kscrolldown(&a);
++ }
+ /* keep a selection being dragged under the pointer */
+ if (buttons & 1)
+ mousesel(e, 0);
- }
- return;
- }
-@@ -501,6 +509,11 @@ bpress(XEvent *e)
++ }
++ return;
++ }
++
+ if (1 <= btn && btn <= 11)
+ buttons |= 1 << (btn-1);
+
+@@ -484,6 +509,11 @@ bpress(XEvent *e)
return;
if (btn == Button1) {