From d52c5e4ce8fb443780936046dae6182ae0e45379 Mon Sep 17 00:00:00 2001 From: bakkeby Date: Mon, 16 Sep 2019 15:31:58 +0200 Subject: [PATCH] Adding scrollback patch --- README.md | 14 ++++- config.def.h | 22 ++++++++ patch/scrollback.c | 38 +++++++++++++ patch/scrollback.h | 21 +++++++ patch/st_include.c | 4 ++ patch/st_include.h | 4 ++ patches.h | 23 ++++++++ st.c | 136 +++++++++++++++++++++++++++++++++++++++++++++ x.c | 43 +++++++++++++- 9 files changed, 302 insertions(+), 3 deletions(-) create mode 100644 patch/scrollback.c create mode 100644 patch/scrollback.h diff --git a/README.md b/README.md index 67f028c..f14cf2d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Refer to [https://dwm.suckless.org/](https://st.suckless.org/) for details on th ### Changelog: -2019-09-16 - Added alpha, anysize, bold-is-not-bright, clipboard, copyurl, disable-fonts, fixime, hidecursor, newterm and open-copied-url patches +2019-09-16 - Added alpha, anysize, bold-is-not-bright, clipboard, copyurl, disable-fonts, fixime, hidecursor, newterm, open-copied-url, vertcenter, scrollback and xresources patches ### Patches included: @@ -47,4 +47,14 @@ Refer to [https://dwm.suckless.org/](https://st.suckless.org/) for details on th - it will have the same CWD (current working directory) as the original st instance - [open-copied-url](https://st.suckless.org/patches/open_copied_url/) - - open contents of the clipboard in a user-defined browser \ No newline at end of file + - open contents of the clipboard in a user-defined browser + + - [scrollback](https://st.suckless.org/patches/scrollback/) + - allows you scroll back through terminal output using keyboard shortcuts or mousewheel + + - [vertcenter](https://st.suckless.org/patches/vertcenter/) + - vertically center lines in the space available if you have set a larger chscale in config.h + + - [xresources](https://st.suckless.org/patches/xresources/) + - adds the ability to configure st via Xresources + - during startup, st will read and apply the resources named in the resources[] array in config.h diff --git a/config.def.h b/config.def.h index fa1d4ea..04f80e0 100644 --- a/config.def.h +++ b/config.def.h @@ -206,10 +206,28 @@ ResourcePref resources[] = { */ static MouseShortcut mshortcuts[] = { /* button mask string */ + #if SCROLLBACK_MOUSE_PATCH || SCROLLBACK_MOUSE_ALTSCREEN_PATCH + { Button4, XK_NO_MOD, "\031" }, + { Button5, XK_NO_MOD, "\005" }, + #else { Button4, XK_ANY_MOD, "\031" }, { Button5, XK_ANY_MOD, "\005" }, + #endif // SCROLLBACK_MOUSE_PATCH / SCROLLBACK_MOUSE_ALTSCREEN_PATCH }; +#if SCROLLBACK_MOUSE_PATCH || SCROLLBACK_MOUSE_ALTSCREEN_PATCH +MouseKey mkeys[] = { + /* button mask function argument */ + #if SCROLLBACK_MOUSE_ALTSCREEN_PATCH + { Button4, XK_NO_MOD, kscrollup, {.i = 1} }, + { Button5, XK_NO_MOD, kscrolldown, {.i = 1} }, + #else + { Button4, ShiftMask, kscrollup, {.i = 1} }, + { Button5, ShiftMask, kscrolldown, {.i = 1} }, + #endif // SCROLLBACK_MOUSE_ALTSCREEN_PATCH +}; +#endif // SCROLLBACK_MOUSE_PATCH / SCROLLBACK_MOUSE_ALTSCREEN_PATCH + /* Internal keyboard shortcuts. */ #define MODKEY Mod1Mask #define TERMMOD (ControlMask|ShiftMask) @@ -225,6 +243,10 @@ static Shortcut shortcuts[] = { { TERMMOD, XK_Home, zoomreset, {.f = 0} }, { TERMMOD, XK_C, clipcopy, {.i = 0} }, { TERMMOD, XK_V, clippaste, {.i = 0} }, + #if SCROLLBACK_PATCH + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, + #endif // SCROLLBACK_PATCH #if CLIPBOARD_PATCH { TERMMOD, XK_Y, clippaste, {.i = 0} }, { ShiftMask, XK_Insert, clippaste, {.i = 0} }, diff --git a/patch/scrollback.c b/patch/scrollback.c new file mode 100644 index 0000000..4eb4fd0 --- /dev/null +++ b/patch/scrollback.c @@ -0,0 +1,38 @@ +void +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (n > term.scr) + n = term.scr; + + if (term.scr > 0) { + term.scr -= n; + selscroll(0, -n); + tfulldirt(); + } +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + if (n < 0) + n = term.row + n; + + if (term.scr <= HISTSIZE-n) { + term.scr += n; + selscroll(0, n); + tfulldirt(); + } +} + +#if SCROLLBACK_MOUSE_ALTSCREEN_PATCH +int tisaltscr(void) +{ + return IS_SET(MODE_ALTSCREEN); +} +#endif // SCROLLBACK_MOUSE_ALTSCREEN_PATCH \ No newline at end of file diff --git a/patch/scrollback.h b/patch/scrollback.h new file mode 100644 index 0000000..5d39d03 --- /dev/null +++ b/patch/scrollback.h @@ -0,0 +1,21 @@ +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ + term.scr + HISTSIZE + 1) % HISTSIZE] : \ + term.line[(y) - term.scr]) + +void kscrolldown(const Arg *); +void kscrollup(const Arg *); + +#if SCROLLBACK_MOUSE_PATCH || SCROLLBACK_MOUSE_ALTSCREEN_PATCH +typedef struct { + uint b; + uint mask; + void (*func)(const Arg *); + const Arg arg; +} MouseKey; + +extern MouseKey mkeys[]; +#endif // SCROLLBACK_MOUSE_PATCH / SCROLLBACK_MOUSE_ALTSCREEN_PATCH + +#if SCROLLBACK_MOUSE_ALTSCREEN_PATCH +int tisaltscr(void); +#endif // SCROLLBACK_MOUSE_ALTSCREEN_PATCH \ No newline at end of file diff --git a/patch/st_include.c b/patch/st_include.c index 38b4ba0..2a706d9 100644 --- a/patch/st_include.c +++ b/patch/st_include.c @@ -6,4 +6,8 @@ #if NEWTERM_PATCH #include "newterm.c" +#endif + +#if SCROLLBACK_PATCH || SCROLLBACK_MOUSE_PATCH || SCROLLBACK_MOUSE_ALTSCREEN_PATCH +#include "scrollback.c" #endif \ No newline at end of file diff --git a/patch/st_include.h b/patch/st_include.h index 9af27f9..2d4c182 100644 --- a/patch/st_include.h +++ b/patch/st_include.h @@ -10,4 +10,8 @@ void xximspot(int, int); #if NEWTERM_PATCH #include "newterm.h" +#endif + +#if SCROLLBACK_PATCH || SCROLLBACK_MOUSE_PATCH || SCROLLBACK_MOUSE_ALTSCREEN_PATCH +#include "scrollback.h" #endif \ No newline at end of file diff --git a/patches.h b/patches.h index 861a53a..1172e01 100644 --- a/patches.h +++ b/patches.h @@ -80,6 +80,29 @@ */ #define OPENCOPIED_PATCH 1 +/* Scroll back through terminal output using Shift+{PageUp, PageDown}. + * https://st.suckless.org/patches/scrollback/ + */ +#define SCROLLBACK_PATCH 1 + +/* Scroll back through terminal output using Shift+MouseWheel. + * This variant depends on SCROLLBACK_PATCH being enabled. + * https://st.suckless.org/patches/scrollback/ + */ +#define SCROLLBACK_MOUSE_PATCH 0 + +/* Scroll back through terminal output using mouse wheel (when not in MODE_ALTSCREEN). + * This variant depends on SCROLLBACK_PATCH being enabled. + * https://st.suckless.org/patches/scrollback/ + */ +#define SCROLLBACK_MOUSE_ALTSCREEN_PATCH 1 + +/* + * Vertically center lines in the space available if you have set a larger chscale in config.h + * https://st.suckless.org/patches/vertcenter/ + */ +#define VERTCENTER_PATCH 1 + /* This patch adds the ability to configure st via Xresources. At startup, st will read and * apply the resources named in the resources[] array in config.h. * https://st.suckless.org/patches/xresources/ diff --git a/st.c b/st.c index 94c6e01..e01d36c 100644 --- a/st.c +++ b/st.c @@ -35,6 +35,9 @@ #define ESC_ARG_SIZ 16 #define STR_BUF_SIZ ESC_BUF_SIZ #define STR_ARG_SIZ ESC_ARG_SIZ +#if SCROLLBACK_PATCH +#define HISTSIZE 2000 +#endif // SCROLLBACK_PATCH /* macros */ #define IS_SET(flag) ((term.mode & (flag)) != 0) @@ -117,6 +120,11 @@ typedef struct { int col; /* nb col */ Line *line; /* screen */ Line *alt; /* alternate screen */ + #if SCROLLBACK_PATCH + Line hist[HISTSIZE]; /* history buffer */ + int histi; /* history index */ + int scr; /* scroll back */ + #endif // SCROLLBACK_PATCH int *dirty; /* dirtyness of lines */ TCursor c; /* cursor */ int ocx; /* old cursor col */ @@ -184,8 +192,13 @@ static void tnewline(int); static void tputtab(int); static void tputc(Rune); static void treset(void); +#if SCROLLBACK_PATCH +static void tscrollup(int, int, int); +static void tscrolldown(int, int, int); +#else static void tscrollup(int, int); static void tscrolldown(int, int); +#endif // SCROLLBACK_PATCH static void tsetattr(int *, int); static void tsetchar(Rune, Glyph *, int, int); static void tsetdirt(int, int); @@ -429,11 +442,19 @@ tlinelen(int y) { int i = term.col; + #if SCROLLBACK_PATCH + if (TLINE(y)[i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && TLINE(y)[i - 1].u == ' ') + --i; + #else if (term.line[y][i - 1].mode & ATTR_WRAP) return i; while (i > 0 && term.line[y][i - 1].u == ' ') --i; + #endif // SCROLLBACK_PATCH return i; } @@ -541,7 +562,11 @@ selsnap(int *x, int *y, int direction) * Snap around if the word wraps around at the end or * beginning of a line. */ + #if SCROLLBACK_PATCH + prevgp = &TLINE(*y)[*x]; + #else prevgp = &term.line[*y][*x]; + #endif // SCROLLBACK_PATCH prevdelim = ISDELIM(prevgp->u); for (;;) { newx = *x + direction; @@ -556,14 +581,22 @@ selsnap(int *x, int *y, int direction) yt = *y, xt = *x; else yt = newy, xt = newx; + #if SCROLLBACK_PATCH + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + #else if (!(term.line[yt][xt].mode & ATTR_WRAP)) + #endif // SCROLLBACK_PATCH break; } if (newx >= tlinelen(newy)) break; + #if SCROLLBACK_PATCH + gp = &TLINE(newy)[newx]; + #else gp = &term.line[newy][newx]; + #endif // SCROLLBACK_PATCH delim = ISDELIM(gp->u); if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim || (delim && gp->u != prevgp->u))) @@ -584,14 +617,22 @@ selsnap(int *x, int *y, int direction) *x = (direction < 0) ? 0 : term.col - 1; if (direction < 0) { for (; *y > 0; *y += direction) { + #if SCROLLBACK_PATCH + if (!(TLINE(*y-1)[term.col-1].mode + #else if (!(term.line[*y-1][term.col-1].mode + #endif // SCROLLBACK_PATCH & ATTR_WRAP)) { break; } } } else if (direction > 0) { for (; *y < term.row-1; *y += direction) { + #if SCROLLBACK_PATCH + if (!(TLINE(*y)[term.col-1].mode + #else if (!(term.line[*y][term.col-1].mode + #endif // SCROLLBACK_PATCH & ATTR_WRAP)) { break; } @@ -622,13 +663,25 @@ getsel(void) } if (sel.type == SEL_RECTANGULAR) { + #if SCROLLBACK_PATCH + gp = &TLINE(y)[sel.nb.x]; + #else gp = &term.line[y][sel.nb.x]; + #endif // SCROLLBACK_PATCH lastx = sel.ne.x; } else { + #if SCROLLBACK_PATCH + gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; + #else gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; + #endif // SCROLLBACK_PATCH lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; } + #if SCROLLBACK_PATCH + last = &TLINE(y)[MIN(lastx, linelen-1)]; + #else last = &term.line[y][MIN(lastx, linelen-1)]; + #endif // SCROLLBACK_PATCH while (last >= gp && last->u == ' ') --last; @@ -851,6 +904,11 @@ void ttywrite(const char *s, size_t n, int may_echo) { const char *next; + #if SCROLLBACK_PATCH + Arg arg = (Arg) { .i = term.scr }; + + kscrolldown(&arg); + #endif // SCROLLBACK_PATCH if (may_echo && IS_SET(MODE_ECHO)) twrite(s, n, 1); @@ -1062,13 +1120,26 @@ tswapscreen(void) } void +#if SCROLLBACK_PATCH +tscrolldown(int orig, int n, int copyhist) +#else tscrolldown(int orig, int n) +#endif // SCROLLBACK_PATCH { int i; Line temp; LIMIT(n, 0, term.bot-orig+1); + #if SCROLLBACK_PATCH + if (copyhist) { + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[term.bot]; + term.line[term.bot] = temp; + } + #endif // SCROLLBACK_PATCH + tsetdirt(orig, term.bot-n); tclearregion(0, term.bot-n+1, term.col-1, term.bot); @@ -1082,13 +1153,29 @@ tscrolldown(int orig, int n) } void +#if SCROLLBACK_PATCH +tscrollup(int orig, int n, int copyhist) +#else tscrollup(int orig, int n) +#endif // SCROLLBACK_PATCH { int i; Line temp; LIMIT(n, 0, term.bot-orig+1); + #if SCROLLBACK_PATCH + if (copyhist) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[orig]; + term.line[orig] = temp; + } + + if (term.scr > 0 && term.scr < HISTSIZE) + term.scr = MIN(term.scr + n, HISTSIZE-1); + #endif // SCROLLBACK_PATCH + tclearregion(0, orig, term.col-1, orig+n-1); tsetdirt(orig+n, term.bot); @@ -1137,7 +1224,11 @@ tnewline(int first_col) int y = term.c.y; if (y == term.bot) { + #if SCROLLBACK_PATCH + tscrollup(term.top, 1, 1); + #else tscrollup(term.top, 1); + #endif // SCROLLBACK_PATCH } else { y++; } @@ -1302,14 +1393,22 @@ void tinsertblankline(int n) { if (BETWEEN(term.c.y, term.top, term.bot)) + #if SCROLLBACK_PATCH + tscrolldown(term.c.y, n, 0); + #else tscrolldown(term.c.y, n); + #endif // SCROLLBACK_PATCH } void tdeleteline(int n) { if (BETWEEN(term.c.y, term.top, term.bot)) + #if SCROLLBACK_PATCH + tscrollup(term.c.y, n, 0); + #else tscrollup(term.c.y, n); + #endif // SCROLLBACK_PATCH } int32_t @@ -1739,11 +1838,19 @@ csihandle(void) break; case 'S': /* SU -- Scroll line up */ DEFAULT(csiescseq.arg[0], 1); + #if SCROLLBACK_PATCH + tscrollup(term.top, csiescseq.arg[0], 0); + #else tscrollup(term.top, csiescseq.arg[0]); + #endif // SCROLLBACK_PATCH break; case 'T': /* SD -- Scroll line down */ DEFAULT(csiescseq.arg[0], 1); + #if SCROLLBACK_PATCH + tscrolldown(term.top, csiescseq.arg[0], 0); + #else tscrolldown(term.top, csiescseq.arg[0]); + #endif // SCROLLBACK_PATCH break; case 'L': /* IL -- Insert blank lines */ DEFAULT(csiescseq.arg[0], 1); @@ -2245,7 +2352,11 @@ eschandle(uchar ascii) return 0; case 'D': /* IND -- Linefeed */ if (term.c.y == term.bot) { + #if SCROLLBACK_PATCH + tscrollup(term.top, 1, 1); + #else tscrollup(term.top, 1); + #endif // SCROLLBACK_PATCH } else { tmoveto(term.c.x, term.c.y+1); } @@ -2258,7 +2369,11 @@ eschandle(uchar ascii) break; case 'M': /* RI -- Reverse index */ if (term.c.y == term.top) { + #if SCROLLBACK_PATCH + tscrolldown(term.top, 1, 1); + #else tscrolldown(term.top, 1); + #endif // SCROLLBACK_PATCH } else { tmoveto(term.c.x, term.c.y-1); } @@ -2478,6 +2593,9 @@ void tresize(int col, int row) { int i; + #if SCROLLBACK_PATCH + int j; + #endif // SCROLLBACK_PATCH int minrow = MIN(row, term.row); int mincol = MIN(col, term.col); int *bp; @@ -2514,6 +2632,16 @@ tresize(int col, int row) term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + #if SCROLLBACK_PATCH + for (i = 0; i < HISTSIZE; i++) { + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); + for (j = mincol; j < col; j++) { + term.hist[i][j] = term.c.attr; + term.hist[i][j].u = ' '; + } + } + #endif // SCROLLBACK_PATCH + /* resize each row to new width, zero-pad if needed */ for (i = 0; i < minrow; i++) { term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); @@ -2571,7 +2699,11 @@ drawregion(int x1, int y1, int x2, int y2) continue; term.dirty[y] = 0; + #if SCROLLBACK_PATCH + xdrawline(TLINE(y), x1, y, x2); + #else xdrawline(term.line[y], x1, y, x2); + #endif // SCROLLBACK_PATCH } } @@ -2592,6 +2724,10 @@ draw(void) cx--; drawregion(0, 0, term.col, term.row); + + #if SCROLLBACK_PATCH + if (term.scr == 0) + #endif // SCROLLBACK_PATCH 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; diff --git a/x.c b/x.c index 9c8492f..cc699ae 100644 --- a/x.c +++ b/x.c @@ -86,6 +86,9 @@ typedef struct { #endif // ANYSIZE_PATCH int ch; /* char height */ int cw; /* char width */ + #if VERTCENTER_PATCH + int cyo; /* char y offset */ + #endif // VERTCENTER_PATCH int mode; /* window state/mode flags */ int cursor; /* cursor style */ } TermWindow; @@ -438,6 +441,9 @@ bpress(XEvent *e) { struct timespec now; MouseShortcut *ms; + #if SCROLLBACK_MOUSE_PATCH || SCROLLBACK_MOUSE_ALTSCREEN_PATCH + MouseKey *mk; + #endif // SCROLLBACK_MOUSE_PATCH / SCROLLBACK_MOUSE_ALTSCREEN_PATCH int snap; if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { @@ -445,6 +451,9 @@ bpress(XEvent *e) return; } + #if SCROLLBACK_MOUSE_ALTSCREEN_PATCH + if (tisaltscr()) + #endif // SCROLLBACK_MOUSE_ALTSCREEN_PATCH for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { if (e->xbutton.button == ms->b && match(ms->mask, e->xbutton.state)) { @@ -453,6 +462,16 @@ bpress(XEvent *e) } } + #if SCROLLBACK_MOUSE_PATCH || SCROLLBACK_MOUSE_ALTSCREEN_PATCH + for (mk = mkeys; mk < mkeys + LEN(mkeys); mk++) { + if (e->xbutton.button == mk->b + && match(mk->mask, e->xbutton.state)) { + mk->func(&mk->arg); + return; + } + } + #endif // SCROLLBACK_MOUSE_PATCH / SCROLLBACK_MOUSE_ALTSCREEN_PATCH + if (e->xbutton.button == Button1) { /* * If the user clicks below predefined timeouts specific @@ -1029,6 +1048,9 @@ xloadfonts(char *fontstr, double fontsize) /* Setting character width and height. */ win.cw = ceilf(dc.font.width * cwscale); win.ch = ceilf(dc.font.height * chscale); + #if VERTCENTER_PATCH + win.cyo = ceilf(dc.font.height * (chscale - 1) / 2); + #endif // VERTCENTER_PATCH FcPatternDel(pattern, FC_SLANT); #if !DISABLE_ITALIC_FONTS_PATCH @@ -1283,7 +1305,12 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x FcCharSet *fccharset; int i, f, numspecs = 0; - for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + #if VERTCENTER_PATCH + for (i = 0, xp = winx, yp = winy + font->ascent + win.cyo; i < len; ++i) + #else + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) + #endif // VERTCENTER_PATCH + { /* Fetch rune and mode for current glyph. */ rune = glyphs[i].u; mode = glyphs[i].mode; @@ -1308,7 +1335,11 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x font = &dc.bfont; frcflags = FRC_BOLD; } + #if VERTCENTER_PATCH + yp = winy + font->ascent + win.cyo; + #else yp = winy + font->ascent; + #endif // VERTCENTER_PATCH } /* Lookup character index with default font. */ @@ -1546,13 +1577,23 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i /* Render underline and strikethrough. */ if (base.mode & ATTR_UNDERLINE) { + #if VERTCENTER_PATCH + XftDrawRect(xw.draw, fg, winx, winy + win.cyo + dc.font.ascent + 1, + width, 1); + #else XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, width, 1); + #endif } if (base.mode & ATTR_STRUCK) { + #if VERTCENTER_PATCH + XftDrawRect(xw.draw, fg, winx, winy + win.cyo + 2 * dc.font.ascent / 3, + width, 1); + #else XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, width, 1); + #endif // VERTCENTER_PATCH } /* Reset clip to none. */