sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | Refs

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:
Mst.suckless.org/patches/scrollback-reflow-standalone/st-scrollback-reflow-standalone-extended-0.9.3.diff | 658++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
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) {