From 09aba355166805a7d56b2703e4b34beb594a3d22 Mon Sep 17 00:00:00 2001 From: Alexander Bocken Date: Sat, 20 Sep 2025 18:50:59 +0200 Subject: [PATCH] st horizontal scrollback feature --- config.h | 4 +++ st.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- st.h | 2 ++ 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/config.h b/config.h index a6ec6c5..67b3249 100644 --- a/config.h +++ b/config.h @@ -275,6 +275,10 @@ static Shortcut shortcuts[] = { { MODKEY, XK_Down, kscrolldown, {.i = 1} }, { MODKEY, XK_u, kscrollup, {.i = -1} }, { MODKEY, XK_d, kscrolldown, {.i = -1} }, + { MODKEY|ShiftMask, XK_h, hscrollright, {.i = 1} }, + { MODKEY|ShiftMask, XK_l, hscrollleft, {.i = 1} }, + { MODKEY|ShiftMask, XK_Left, hscrollright, {.i = 1} }, + { MODKEY|ShiftMask, XK_Right, hscrollleft, {.i = 1} }, { MODKEY, XK_s, changealpha, {.f = -0.05} }, { MODKEY, XK_a, changealpha, {.f = +0.05} }, { TERMMOD, XK_Up, zoom, {.f = +1} }, diff --git a/st.c b/st.c index 77e8d29..50429fc 100644 --- a/st.c +++ b/st.c @@ -126,6 +126,9 @@ typedef struct { Line hist[HISTSIZE]; /* history buffer */ int histi; /* history index */ int scr; /* scroll back */ + int hscr; /* horizontal scroll offset */ + int *linelen; /* actual length of each line */ + int *histlen; /* actual length of each history line */ int *dirty; /* dirtyness of lines */ TCursor c; /* cursor */ int ocx; /* old cursor col */ @@ -1288,6 +1291,52 @@ kscrollup(const Arg* a) } } +void +hscrollleft(const Arg* a) +{ + int n = a->i; + int maxscroll = 0; + int i; + + if (n < 0) + n = term.col + n; + + /* find maximum horizontal scroll based on line lengths */ + if (term.scr == 0) { + /* scrolling current screen */ + for (i = 0; i < term.row; i++) { + if (term.linelen[i] > maxscroll) + maxscroll = term.linelen[i]; + } + } else { + /* scrolling history */ + for (i = 0; i < HISTSIZE; i++) { + if (term.histlen[i] > maxscroll) + maxscroll = term.histlen[i]; + } + } + + maxscroll = MAX(0, maxscroll - term.col); + if (term.hscr < maxscroll) { + term.hscr = MIN(term.hscr + n, maxscroll); + tfulldirt(); + } +} + +void +hscrollright(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.col + n; + + if (term.hscr > 0) { + term.hscr = MAX(0, term.hscr - n); + tfulldirt(); + } +} + void tscrolldown(int orig, int n, int copyhist) { @@ -1469,6 +1518,10 @@ tsetchar(Rune u, const Glyph *attr, int x, int y) term.line[y][x] = *attr; term.line[y][x].u = u; + /* update line length tracking */ + if (x + 1 > term.linelen[y]) + term.linelen[y] = x + 1; + if (isboxdraw(u)) term.line[y][x].mode |= ATTR_BOXDRAW; } @@ -2882,18 +2935,25 @@ tresize(int col, int row) term.alt = xrealloc(term.alt, row * sizeof(Line)); term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + term.linelen = xrealloc(term.linelen, row * sizeof(*term.linelen)); + term.histlen = xrealloc(term.histlen, HISTSIZE * sizeof(*term.histlen)); + /* preserve history line lengths and resize history buffer */ for (i = 0; i < HISTSIZE; i++) { - term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); + if (!term.histlen[i]) + term.histlen[i] = term.maxcol; + term.hist[i] = xrealloc(term.hist[i], MAX(col, term.histlen[i]) * sizeof(Glyph)); for (j = mincol; j < col; j++) { term.hist[i][j] = term.c.attr; term.hist[i][j].u = ' '; } } - /* resize each row to new width, zero-pad if needed */ + /* resize each row to new width, preserve line lengths */ for (i = 0; i < minrow; i++) { - term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + if (!term.linelen[i]) + term.linelen[i] = term.maxcol; + term.line[i] = xrealloc(term.line[i], MAX(col, term.linelen[i]) * sizeof(Glyph)); term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); } @@ -2901,6 +2961,7 @@ tresize(int col, int row) for (/* i = minrow */; i < row; i++) { term.line[i] = xmalloc(col * sizeof(Glyph)); term.alt[i] = xmalloc(col * sizeof(Glyph)); + term.linelen[i] = col; } if (col > term.maxcol) { bp = term.tabs + term.maxcol; @@ -2944,13 +3005,32 @@ void drawregion(int x1, int y1, int x2, int y2) { int y; + Line line; + int hx1, hx2, linelen; for (y = y1; y < y2; y++) { if (!term.dirty[y]) continue; term.dirty[y] = 0; - xdrawline(TLINE(y), x1, y, x2); + line = TLINE(y); + + /* get line length based on whether we're in history or current screen */ + if (y < term.scr) { + /* history line */ + int histidx = ((y + term.histi - term.scr + HISTSIZE + 1) % HISTSIZE); + linelen = term.histlen[histidx]; + } else { + /* current screen line */ + linelen = term.linelen[y - term.scr]; + } + + /* apply horizontal scroll offset */ + if (term.hscr >= linelen) + continue; + + /* draw from horizontally scrolled position */ + xdrawline(&line[term.hscr], x1, y, MIN(x2, linelen - term.hscr)); } } diff --git a/st.h b/st.h index 507b862..3c964a5 100644 --- a/st.h +++ b/st.h @@ -88,6 +88,8 @@ void draw(void); void externalpipe(const Arg *); void kscrolldown(const Arg *); void kscrollup(const Arg *); +void hscrollleft(const Arg *); +void hscrollright(const Arg *); void newterm(const Arg *); void printscreen(const Arg *);