st

fork of suckless's simple terminal
Index Commits Files Refs README LICENSE
commit 5016961ef88fa9b77fcb98d241539cf5e419cd7e
parent 343dfc629dde00bade3a35716ef14552699322c1
Author: mjkloeckner <martin.cachari@gmail.com>
Date:   Tue,  1 Nov 2022 23:55:45 -0300

Update

Diffstat:
Mconfig.def.h | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Apatches/st-blinking_cursor-20211116-2f6e597.diff | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatches/st-colorschemes-0.8.5.diff | 306+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatches/st-scrollback-20210507-4536f46.diff | 351+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatches/st-scrollback-mouse-20220127-2c5edf2.diff | 25+++++++++++++++++++++++++
Apatches/st-scrollback-reflow-0.8.5.diff | 1478+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatches/st-scrollback-ringbuffer-0.8.5.diff | 730+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatches/st-universcroll-0.8.4.diff | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mst.c | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mst.h | 5+++++
Atags | 577+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mx.c | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
12 files changed, 4094 insertions(+), 107 deletions(-)
diff --git a/config.def.h b/config.def.h
@@ -6,9 +6,9 @@
  * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
  */
 /* static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; */
-static char *font = "JetBrains Mono NL:size=10:antialias=true:autohint=true";
-static int borderpx = 2;
-
+/* static char *font = "JetBrainsMono Nerd Font Mono:style=Regular:size=10:antialias=true:autohint=true"; */
+static char *font = "Victor Mono:style=Regular:size=10:antialias=true:autohint=true";
+static int borderpx = 0;
 /*
  * What program is execed by st depends of these precedence rules:
  * 1: program passed with -e
@@ -20,7 +20,7 @@ static int borderpx = 2;
 static char *shell = "/bin/sh";
 char *utmp = NULL;
 /* scroll program: to enable use a string like "scroll" */
-char *scroll = "scroll";
+char *scroll = NULL;
 char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400";
 
 /* identification sequence returned in DA and DECID */
@@ -97,53 +97,128 @@ unsigned int tabspaces = 8;
 /* bg opacity */
 float alpha = 0.92;
 
-/* #1d2021 */
-/* Terminal colors (16 first used in escape sequence) */
-static const char *colorname[] = {
+typedef struct {
+    const char* const colors[258]; /* terminal colors */
+    unsigned int fg;               /* foreground */
+    unsigned int bg;               /* background */
+    unsigned int cs;               /* cursor */
+    unsigned int rcs;              /* reverse cursor */
+} ColorScheme;
+/*
+ * Terminal colors (16 first used in escape sequence,
+ * 2 last for custom cursor color),
+ * foreground, background, cursor, reverse cursor
+ */
+static const ColorScheme schemes[] = {
+    /* // st (dark) */
+    /* {{"black", "red3", "green3", "yellow3", */
+    /*   "blue2", "magenta3", "cyan3", "gray90", */
+    /*   "gray50", "red", "green", "yellow", */
+    /*   "#5c5cff", "magenta", "cyan", "white", */
+    /*   [256]="#cccccc", "#555555"}, 7, 0, 256, 257}, */
+
     /* 8 normal colors */
-    [0] = "#0c1010", /* hard contrast: #1d2021 / soft contrast: #32302f */
-    [1] = "#cc241d", /* red     */
-    [2] = "#98971a", /* green   */
-    [3] = "#d79921", /* yellow  */
-    [4] = "#458588", /* blue    */
-    [5] = "#b16286", /* magenta */
-    [6] = "#689d6a", /* cyan    */
-    [7] = "#a89984", /* white   */
-
-    /* 8 bright colors */
-    [8]  = "#928374", /* black   */
-    [9]  = "#fb4934", /* red     */
-    [10] = "#b8bb26", /* green   */
-    [11] = "#fabd2f", /* yellow  */
-    [12] = "#83a598", /* blue    */
-    [13] = "#d3869b", /* magenta */
-    [14] = "#8ec07c", /* cyan    */
-    [15] = "#ebdbb2", /* white   */
+    {{  "#040505", /* hard contrast: #1d2021 / soft contrast: #32302f */
+        "#cc241d", /* red     */
+        "#98971a", /* green   */
+        "#d79921", /* yellow  */
+        "#458588", /* blue    */
+        "#b16286", /* magenta */
+        "#689d6a", /* cyan    */
+        "#a89984", /* white   */
+
+        /* 8 bright colors */
+        "#928374", /* black   */
+        "#fb4934", /* red     */
+        "#b8bb26", /* green   */
+        "#fabd2f", /* yellow  */
+        "#83a598", /* blue    */
+        "#d3869b", /* magenta */
+        "#8ec07c", /* cyan    */
+        "#ebdbb2", /* white   */
+        [256]="#a89984", "#040505"}, 15, 0, 256, 257},
+
+    // Alacritty (dark)
+    {{"#1d1f21", "#cc6666", "#b5bd68", "#f0c674",
+      "#81a2be", "#b294bb", "#8abeb7", "#c5c8c6",
+      "#666666", "#d54e53", "#b9ca4a", "#e7c547",
+      "#7aa6da", "#c397d8", "#70c0b1", "#eaeaea",
+      [256]="#cccccc", "#555555"}, 7, 0, 256, 257},
+
+    // One Half dark
+    {{"#282c34", "#e06c75", "#98c379", "#e5c07b",
+      "#61afef", "#c678dd", "#56b6c2", "#dcdfe4",
+      "#282c34", "#e06c75", "#98c379", "#e5c07b",
+      "#61afef", "#c678dd", "#56b6c2", "#dcdfe4",
+      [256]="#cccccc", "#555555"}, 7, 0, 256, 257},
+
+    // One Half light
+    {{"#fafafa", "#e45649", "#50a14f", "#c18401",
+      "#0184bc", "#a626a4", "#0997b3", "#383a42",
+      "#fafafa", "#e45649", "#50a14f", "#c18401",
+      "#0184bc", "#a626a4", "#0997b3", "#383a42",
+      [256]="#cccccc", "#555555"}, 7, 0, 256, 257},
+
+    // Solarized dark
+    {{"#073642", "#dc322f", "#859900", "#b58900",
+      "#268bd2", "#d33682", "#2aa198", "#eee8d5",
+      "#002b36", "#cb4b16", "#586e75", "#657b83",
+      "#839496", "#6c71c4", "#93a1a1", "#fdf6e3",
+      [256]="#93a1a1", "#fdf6e3"}, 12, 8, 256, 257},
+
+    // Solarized light
+    {{"#eee8d5", "#dc322f", "#859900", "#b58900",
+      "#268bd2", "#d33682", "#2aa198", "#073642",
+      "#fdf6e3", "#cb4b16", "#93a1a1", "#839496",
+      "#657b83", "#6c71c4", "#586e75", "#002b36",
+      [256]="#586e75", "#002b36"}, 12, 8, 256, 257},
+
+    // Gruvbox dark
+    {{"#282828", "#cc241d", "#98971a", "#d79921",
+      "#458588", "#b16286", "#689d6a", "#a89984",
+      "#928374", "#fb4934", "#b8bb26", "#fabd2f",
+      "#83a598", "#d3869b", "#8ec07c", "#ebdbb2",
+      [256]="#ebdbb2", "#555555"}, 15, 0, 256, 257},
+
+    // Gruvbox light
+    {{"#fbf1c7", "#cc241d", "#98971a", "#d79921",
+      "#458588", "#b16286", "#689d6a", "#7c6f64",
+      "#928374", "#9d0006", "#79740e", "#b57614",
+      "#076678", "#8f3f71", "#427b58", "#3c3836",
+      [256]="#3c3836", "#555555"}, 15, 0, 256, 257},
 };
 
+static const char * const * colorname;
+int colorscheme = 0;
 
 /*
  * Default colors (colorname index)
  * foreground, background, cursor, reverse cursor
  */
-unsigned int defaultfg = 15;
-unsigned int defaultbg = 0;
-unsigned int defaultcs = 15;
-static unsigned int defaultrcs = 257;
+unsigned int defaultfg;
+unsigned int defaultbg;
+unsigned int defaultcs;
+static unsigned int defaultrcs;
 
 /*
- * Default shape of cursor
- * 2: Block ("█")
- * 4: Underline ("_")
- * 6: Bar ("|")
- * 7: Snowman ("☃")
+ * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-Ps-SP-q.1D81
+ * Default style of cursor
+ * 0: blinking block
+ * 1: blinking block (default)
+ * 2: steady block ("█")
+ * 3: blinking underline
+ * 4: steady underline ("_")
+ * 5: blinking bar
+ * 6: steady bar ("|")
+ * 7: blinking st cursor
+ * 8: steady st cursor
  */
-static unsigned int cursorshape = 2;
+static unsigned int cursorstyle = 1;
+static Rune stcursor = 0x2603; /* snowman ("☃") */
 
 /*
  * Default columns and rows numbers
  */
-
 static unsigned int cols = 80;
 static unsigned int rows = 24;
 
@@ -173,17 +248,26 @@ static uint forcemousemod = ShiftMask;
  */
 static MouseShortcut mshortcuts[] = {
     /* mask                 button   function        argument       release */
-    { XK_ANY_MOD,           Button2, selpaste,       {.i = 0},      1 },
-    { ShiftMask,            Button4, ttysend,        {.s = "\033[5;2~"} },
-    { XK_ANY_MOD,           Button4, ttysend,        {.s = "\031"} },
-    { ShiftMask,            Button5, ttysend,        {.s = "\033[6;2~"} },
-    { XK_ANY_MOD,           Button5, ttysend,        {.s = "\005"} },
+    { ShiftMask,            Button4, kscrollup,      {.i = 1} },
+    { ShiftMask,            Button5, kscrolldown,    {.i = 1} },
+    /* mask                 button   function        argument      release alt */
+    { XK_ANY_MOD,           Button2, selpaste,       {.i = 0},           1 },
+    { XK_ANY_MOD,           Button4, ttysend,        {.s = "\033[5;2~"}, 0, -1 },
+    { XK_ANY_MOD,           Button4, ttysend,        {.s = "\033[5;2~"} },
+    { XK_ANY_MOD,           Button5, ttysend,        {.s = "\033[6;2~"}, 0, -1 },
+    { XK_ANY_MOD,           Button5, ttysend,        {.s = "\033[6:2~"} },
 };
 
 /* Internal keyboard shortcuts. */
 #define MODKEY Mod1Mask
 #define TERMMOD (ControlMask|ShiftMask)
 
+static unsigned char scheme = 0;
+static void togglescheme() {
+    Arg s = {.i = (scheme = (scheme ? 0 : 7))};
+    selectscheme(&s);
+}
+
 static Shortcut shortcuts[] = {
     /* mask                 keysym          function        argument */
     { XK_ANY_MOD,           XK_Break,       sendbreak,      {.i =  0} },
@@ -198,6 +282,18 @@ static Shortcut shortcuts[] = {
     { TERMMOD,              XK_Y,           selpaste,       {.i =  0} },
     { ShiftMask,            XK_Insert,      selpaste,       {.i =  0} },
     { TERMMOD,              XK_Num_Lock,    numlock,        {.i =  0} },
+    { MODKEY,               XK_1,           selectscheme,   {.i =  0} },
+    { MODKEY,               XK_2,           selectscheme,   {.i =  1} },
+    { MODKEY,               XK_3,           selectscheme,   {.i =  2} },
+    { MODKEY,               XK_4,           selectscheme,   {.i =  3} },
+    { MODKEY,               XK_5,           selectscheme,   {.i =  4} },
+    { MODKEY,               XK_6,           selectscheme,   {.i =  5} },
+    { MODKEY,               XK_7,           selectscheme,   {.i =  6} },
+    { MODKEY,               XK_8,           selectscheme,   {.i =  7} },
+    { MODKEY,               XK_9,           selectscheme,   {.i =  8} },
+    { MODKEY,               XK_0,           togglescheme,   {.i = -1} },
+    { ShiftMask,            XK_Page_Up,     kscrollup,      {.i = -1} },
+    { ShiftMask,            XK_Page_Down,   kscrolldown,    {.i = -1} },
 };
 
 /*
diff --git a/patches/st-blinking_cursor-20211116-2f6e597.diff b/patches/st-blinking_cursor-20211116-2f6e597.diff
@@ -0,0 +1,153 @@
+From a3cdd0753bf578cd4e6db7c6507481f3b5c38aea Mon Sep 17 00:00:00 2001
+From: Steve Ward <planet36@gmail.com>
+Date: Tue, 16 Nov 2021 14:15:06 -0500
+Subject: [PATCH] Allow blinking cursor
+
+---
+ config.def.h | 19 +++++++++++++------
+ x.c          | 47 +++++++++++++++++++++++++++++++++++------------
+ 2 files changed, 48 insertions(+), 18 deletions(-)
+
+diff --git a/config.def.h b/config.def.h
+index 6f05dce..1a5fed0 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -133,13 +133,20 @@ static unsigned int defaultcs = 256;
+ static unsigned int defaultrcs = 257;
+ 
+ /*
+- * Default shape of cursor
+- * 2: Block ("█")
+- * 4: Underline ("_")
+- * 6: Bar ("|")
+- * 7: Snowman ("☃")
++ * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-Ps-SP-q.1D81
++ * Default style of cursor
++ * 0: blinking block
++ * 1: blinking block (default)
++ * 2: steady block ("█")
++ * 3: blinking underline
++ * 4: steady underline ("_")
++ * 5: blinking bar
++ * 6: steady bar ("|")
++ * 7: blinking st cursor
++ * 8: steady st cursor
+  */
+-static unsigned int cursorshape = 2;
++static unsigned int cursorstyle = 1;
++static Rune stcursor = 0x2603; /* snowman ("☃") */
+ 
+ /*
+  * Default columns and rows numbers
+diff --git a/x.c b/x.c
+index 89786b8..7d2447d 100644
+--- a/x.c
++++ b/x.c
+@@ -253,6 +253,7 @@ static char *opt_name  = NULL;
+ static char *opt_title = NULL;
+ 
+ static int oldbutton = 3; /* button event on startup: 3 = release */
++static int cursorblinks = 0;
+ 
+ void
+ clipcopy(const Arg *dummy)
+@@ -1529,29 +1530,44 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
+     /* draw the new one */
+     if (IS_SET(MODE_FOCUSED)) {
+         switch (win.cursor) {
+-        case 7: /* st extension */
+-            g.u = 0x2603; /* snowman (U+2603) */
++        default:
++        case 0: /* blinking block */
++        case 1: /* blinking block (default) */
++            if (IS_SET(MODE_BLINK))
++                break;
+             /* FALLTHROUGH */
+-        case 0: /* Blinking Block */
+-        case 1: /* Blinking Block (Default) */
+-        case 2: /* Steady Block */
++        case 2: /* steady block */
+             xdrawglyph(g, cx, cy);
+             break;
+-        case 3: /* Blinking Underline */
+-        case 4: /* Steady Underline */
++        case 3: /* blinking underline */
++            if (IS_SET(MODE_BLINK))
++                break;
++            /* FALLTHROUGH */
++        case 4: /* steady underline */
+             XftDrawRect(xw.draw, &drawcol,
+                     borderpx + cx * win.cw,
+                     borderpx + (cy + 1) * win.ch - \
+                         cursorthickness,
+                     win.cw, cursorthickness);
+             break;
+-        case 5: /* Blinking bar */
+-        case 6: /* Steady bar */
++        case 5: /* blinking bar */
++            if (IS_SET(MODE_BLINK))
++                break;
++            /* FALLTHROUGH */
++        case 6: /* steady bar */
+             XftDrawRect(xw.draw, &drawcol,
+                     borderpx + cx * win.cw,
+                     borderpx + cy * win.ch,
+                     cursorthickness, win.ch);
+             break;
++        case 7: /* blinking st cursor */
++            if (IS_SET(MODE_BLINK))
++                break;
++            /* FALLTHROUGH */
++        case 8: /* steady st cursor */
++            g.u = stcursor;
++            xdrawglyph(g, cx, cy);
++            break;
+         }
+     } else {
+         XftDrawRect(xw.draw, &drawcol,
+@@ -1708,9 +1724,12 @@ xsetmode(int set, unsigned int flags)
+ int
+ xsetcursor(int cursor)
+ {
+-    if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */
++    if (!BETWEEN(cursor, 0, 8)) /* 7-8: st extensions */
+         return 1;
+     win.cursor = cursor;
++    cursorblinks = win.cursor == 0 || win.cursor == 1 ||
++                   win.cursor == 3 || win.cursor == 5 ||
++                   win.cursor == 7;
+     return 0;
+ }
+ 
+@@ -1954,6 +1973,10 @@ run(void)
+         if (FD_ISSET(ttyfd, &rfd) || xev) {
+             if (!drawing) {
+                 trigger = now;
++                if (IS_SET(MODE_BLINK)) {
++                    win.mode ^= MODE_BLINK;
++                }
++                lastblink = now;
+                 drawing = 1;
+             }
+             timeout = (maxlatency - TIMEDIFF(now, trigger)) \
+@@ -1964,7 +1987,7 @@ run(void)
+ 
+         /* idle detected or maxlatency exhausted -> draw */
+         timeout = -1;
+-        if (blinktimeout && tattrset(ATTR_BLINK)) {
++        if (blinktimeout && (cursorblinks || tattrset(ATTR_BLINK))) {
+             timeout = blinktimeout - TIMEDIFF(now, lastblink);
+             if (timeout <= 0) {
+                 if (-timeout > blinktimeout) /* start visible */
+@@ -2000,7 +2023,7 @@ main(int argc, char *argv[])
+ {
+     xw.l = xw.t = 0;
+     xw.isfixed = False;
+-    xsetcursor(cursorshape);
++    xsetcursor(cursorstyle);
+ 
+     ARGBEGIN {
+     case 'a':
+-- 
+2.34.0
+
diff --git a/patches/st-colorschemes-0.8.5.diff b/patches/st-colorschemes-0.8.5.diff
@@ -0,0 +1,306 @@
+From 9bfbafa1e98c13c039bea4790941e51b3a8054b4 Mon Sep 17 00:00:00 2001
+From: Max Schillinger <maxschillinger@web.de>
+Date: Thu, 23 Jun 2022 21:58:37 +0200
+Subject: [PATCH] Add multiple color schemes and key bindings to change them
+
+This commits adds these color schemes:
+
+- the default (dark) st color scheme
+- the default (dark) alacritty color scheme
+- One Half (dark & light)
+- Solarized (dark & light)
+- Gruvbox (dark & light)
+
+Select one with Alt+1..8.
+Select the next one with Alt+0.
+Select the previous one with Ctrl+Alt+0.
+---
+ config.def.h | 118 +++++++++++++++++++++++++++++++++++++--------------
+ st.c         |  22 ++++++++++
+ st.h         |   2 +
+ x.c          |  52 ++++++++++++++++++++++-
+ 4 files changed, 160 insertions(+), 34 deletions(-)
+
+diff --git a/config.def.h b/config.def.h
+index 91ab8ca..38777fe 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -93,46 +93,87 @@ char *termname = "st-256color";
+  */
+ unsigned int tabspaces = 8;
+ 
+-/* Terminal colors (16 first used in escape sequence) */
+-static const char *colorname[] = {
+-    /* 8 normal colors */
+-    "black",
+-    "red3",
+-    "green3",
+-    "yellow3",
+-    "blue2",
+-    "magenta3",
+-    "cyan3",
+-    "gray90",
+-
+-    /* 8 bright colors */
+-    "gray50",
+-    "red",
+-    "green",
+-    "yellow",
+-    "#5c5cff",
+-    "magenta",
+-    "cyan",
+-    "white",
+-
+-    [255] = 0,
+-
+-    /* more colors can be added after 255 to use with DefaultXX */
+-    "#cccccc",
+-    "#555555",
+-    "gray90", /* default foreground colour */
+-    "black", /* default background colour */
++typedef struct {
++    const char* const colors[258]; /* terminal colors */
++    unsigned int fg;               /* foreground */
++    unsigned int bg;               /* background */
++    unsigned int cs;               /* cursor */
++    unsigned int rcs;              /* reverse cursor */
++} ColorScheme;
++/*
++ * Terminal colors (16 first used in escape sequence,
++ * 2 last for custom cursor color),
++ * foreground, background, cursor, reverse cursor
++ */
++static const ColorScheme schemes[] = {
++    // st (dark)
++    {{"black", "red3", "green3", "yellow3",
++      "blue2", "magenta3", "cyan3", "gray90",
++      "gray50", "red", "green", "yellow",
++      "#5c5cff", "magenta", "cyan", "white",
++      [256]="#cccccc", "#555555"}, 7, 0, 256, 257},
++
++    // Alacritty (dark)
++    {{"#1d1f21", "#cc6666", "#b5bd68", "#f0c674",
++      "#81a2be", "#b294bb", "#8abeb7", "#c5c8c6",
++      "#666666", "#d54e53", "#b9ca4a", "#e7c547",
++      "#7aa6da", "#c397d8", "#70c0b1", "#eaeaea",
++      [256]="#cccccc", "#555555"}, 7, 0, 256, 257},
++
++    // One Half dark
++    {{"#282c34", "#e06c75", "#98c379", "#e5c07b",
++      "#61afef", "#c678dd", "#56b6c2", "#dcdfe4",
++      "#282c34", "#e06c75", "#98c379", "#e5c07b",
++      "#61afef", "#c678dd", "#56b6c2", "#dcdfe4",
++      [256]="#cccccc", "#555555"}, 7, 0, 256, 257},
++
++    // One Half light
++    {{"#fafafa", "#e45649", "#50a14f", "#c18401",
++      "#0184bc", "#a626a4", "#0997b3", "#383a42",
++      "#fafafa", "#e45649", "#50a14f", "#c18401",
++      "#0184bc", "#a626a4", "#0997b3", "#383a42",
++      [256]="#cccccc", "#555555"}, 7, 0, 256, 257},
++
++    // Solarized dark
++    {{"#073642", "#dc322f", "#859900", "#b58900",
++      "#268bd2", "#d33682", "#2aa198", "#eee8d5",
++      "#002b36", "#cb4b16", "#586e75", "#657b83",
++      "#839496", "#6c71c4", "#93a1a1", "#fdf6e3",
++      [256]="#93a1a1", "#fdf6e3"}, 12, 8, 256, 257},
++
++    // Solarized light
++    {{"#eee8d5", "#dc322f", "#859900", "#b58900",
++      "#268bd2", "#d33682", "#2aa198", "#073642",
++      "#fdf6e3", "#cb4b16", "#93a1a1", "#839496",
++      "#657b83", "#6c71c4", "#586e75", "#002b36",
++      [256]="#586e75", "#002b36"}, 12, 8, 256, 257},
++
++    // Gruvbox dark
++    {{"#282828", "#cc241d", "#98971a", "#d79921",
++      "#458588", "#b16286", "#689d6a", "#a89984",
++      "#928374", "#fb4934", "#b8bb26", "#fabd2f",
++      "#83a598", "#d3869b", "#8ec07c", "#ebdbb2",
++      [256]="#ebdbb2", "#555555"}, 15, 0, 256, 257},
++
++    // Gruvbox light
++    {{"#fbf1c7", "#cc241d", "#98971a", "#d79921",
++      "#458588", "#b16286", "#689d6a", "#7c6f64",
++      "#928374", "#9d0006", "#79740e", "#b57614",
++      "#076678", "#8f3f71", "#427b58", "#3c3836",
++      [256]="#3c3836", "#555555"}, 15, 0, 256, 257},
+ };
+ 
++static const char * const * colorname;
++int colorscheme = 0;
+ 
+ /*
+  * Default colors (colorname index)
+  * foreground, background, cursor, reverse cursor
+  */
+-unsigned int defaultfg = 258;
+-unsigned int defaultbg = 259;
+-unsigned int defaultcs = 256;
+-static unsigned int defaultrcs = 257;
++unsigned int defaultfg;
++unsigned int defaultbg;
++unsigned int defaultcs;
++static unsigned int defaultrcs;
+ 
+ /*
+  * Default shape of cursor
+@@ -201,6 +242,17 @@ static Shortcut shortcuts[] = {
+     { TERMMOD,              XK_Y,           selpaste,       {.i =  0} },
+     { ShiftMask,            XK_Insert,      selpaste,       {.i =  0} },
+     { TERMMOD,              XK_Num_Lock,    numlock,        {.i =  0} },
++    { MODKEY,               XK_1,           selectscheme,   {.i =  0} },
++    { MODKEY,               XK_2,           selectscheme,   {.i =  1} },
++    { MODKEY,               XK_3,           selectscheme,   {.i =  2} },
++    { MODKEY,               XK_4,           selectscheme,   {.i =  3} },
++    { MODKEY,               XK_5,           selectscheme,   {.i =  4} },
++    { MODKEY,               XK_6,           selectscheme,   {.i =  5} },
++    { MODKEY,               XK_7,           selectscheme,   {.i =  6} },
++    { MODKEY,               XK_8,           selectscheme,   {.i =  7} },
++    { MODKEY,               XK_9,           selectscheme,   {.i =  8} },
++    { MODKEY,               XK_0,           nextscheme,     {.i = +1} },
++    { MODKEY|ControlMask,   XK_0,           nextscheme,     {.i = -1} },
+ };
+ 
+ /*
+diff --git a/st.c b/st.c
+index 51049ba..3ffe333 100644
+--- a/st.c
++++ b/st.c
+@@ -2196,6 +2196,28 @@ tstrsequence(uchar c)
+     term.esc |= ESC_STR;
+ }
+ 
++void
++tupdatebgcolor(int oldbg, int newbg)
++{
++    for (int y = 0; y < term.row; y++) {
++        for (int x = 0; x < term.col; x++) {
++            if (term.line[y][x].bg == oldbg)
++                term.line[y][x].bg = newbg;
++        }
++    }
++}
++
++void
++tupdatefgcolor(int oldfg, int newfg)
++{
++    for (int y = 0; y < term.row; y++) {
++        for (int x = 0; x < term.col; x++) {
++            if (term.line[y][x].fg == oldfg)
++                term.line[y][x].fg = newfg;
++        }
++    }
++}
++
+ void
+ tcontrolcode(uchar ascii)
+ {
+diff --git a/st.h b/st.h
+index 519b9bd..2700de5 100644
+--- a/st.h
++++ b/st.h
+@@ -90,6 +90,8 @@ int tattrset(int);
+ void tnew(int, int);
+ void tresize(int, int);
+ void tsetdirtattr(int);
++void tupdatebgcolor(int, int);
++void tupdatefgcolor(int, int);
+ void ttyhangup(void);
+ int ttynew(const char *, char *, const char *, char **);
+ size_t ttyread(void);
+diff --git a/x.c b/x.c
+index 8a16faa..bc0a48c 100644
+--- a/x.c
++++ b/x.c
+@@ -59,6 +59,8 @@ static void zoom(const Arg *);
+ static void zoomabs(const Arg *);
+ static void zoomreset(const Arg *);
+ static void ttysend(const Arg *);
++static void nextscheme(const Arg *);
++static void selectscheme(const Arg *);
+ 
+ /* config.h for applying patches and the configuration. */
+ #include "config.h"
+@@ -185,6 +187,7 @@ static void mousesel(XEvent *, int);
+ static void mousereport(XEvent *);
+ static char *kmap(KeySym, uint);
+ static int match(uint, uint);
++static void updatescheme(void);
+ 
+ static void run(void);
+ static void usage(void);
+@@ -785,7 +788,7 @@ xloadcols(void)
+         for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp)
+             XftColorFree(xw.dpy, xw.vis, xw.cmap, cp);
+     } else {
+-        dc.collen = MAX(LEN(colorname), 256);
++        dc.collen = 258;
+         dc.col = xmalloc(dc.collen * sizeof(Color));
+     }
+ 
+@@ -2008,6 +2011,47 @@ usage(void)
+         " [stty_args ...]\n", argv0, argv0);
+ }
+ 
++void
++nextscheme(const Arg *arg)
++{
++    colorscheme += arg->i;
++    if (colorscheme >= (int)LEN(schemes))
++        colorscheme = 0;
++    else if (colorscheme < 0)
++        colorscheme = LEN(schemes) - 1;
++    updatescheme();
++}
++
++void
++selectscheme(const Arg *arg)
++{
++    if (BETWEEN(arg->i, 0, LEN(schemes)-1)) {
++        colorscheme = arg->i;
++        updatescheme();
++    }
++}
++
++void
++updatescheme(void)
++{
++    int oldbg, oldfg;
++
++    oldbg = defaultbg;
++    oldfg = defaultfg;
++    colorname = schemes[colorscheme].colors;
++    defaultbg = schemes[colorscheme].bg;
++    defaultfg = schemes[colorscheme].fg;
++    defaultcs = schemes[colorscheme].cs;
++    defaultrcs = schemes[colorscheme].rcs;
++    xloadcols();
++    if (defaultbg != oldbg)
++        tupdatebgcolor(oldbg, defaultbg);
++    if (defaultfg != oldfg)
++        tupdatefgcolor(oldfg, defaultfg);
++    cresize(win.w, win.h);
++    redraw();
++}
++
+ int
+ main(int argc, char *argv[])
+ {
+@@ -2060,6 +2104,12 @@ main(int argc, char *argv[])
+     } ARGEND;
+ 
+ run:
++    colorname = schemes[colorscheme].colors;
++    defaultbg = schemes[colorscheme].bg;
++    defaultfg = schemes[colorscheme].fg;
++    defaultcs = schemes[colorscheme].cs;
++    defaultrcs = schemes[colorscheme].rcs;
++
+     if (argc > 0) /* eat all remaining arguments */
+         opt_cmd = argv;
+ 
+-- 
+2.36.1
+
diff --git a/patches/st-scrollback-20210507-4536f46.diff b/patches/st-scrollback-20210507-4536f46.diff
@@ -0,0 +1,351 @@
+diff --git a/config.def.h b/config.def.h
+index 6f05dce..93cbcc0 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -199,6 +199,8 @@ static Shortcut shortcuts[] = {
+     { TERMMOD,              XK_Y,           selpaste,       {.i =  0} },
+     { ShiftMask,            XK_Insert,      selpaste,       {.i =  0} },
+     { TERMMOD,              XK_Num_Lock,    numlock,        {.i =  0} },
++    { ShiftMask,            XK_Page_Up,     kscrollup,      {.i = -1} },
++    { ShiftMask,            XK_Page_Down,   kscrolldown,    {.i = -1} },
+ };
+ 
+ /*
+diff --git a/st.c b/st.c
+index ebdf360..817cc47 100644
+--- a/st.c
++++ b/st.c
+@@ -35,6 +35,7 @@
+ #define ESC_ARG_SIZ   16
+ #define STR_BUF_SIZ   ESC_BUF_SIZ
+ #define STR_ARG_SIZ   ESC_ARG_SIZ
++#define HISTSIZE      2000
+ 
+ /* macros */
+ #define IS_SET(flag)        ((term.mode & (flag)) != 0)
+@@ -42,6 +43,9 @@
+ #define ISCONTROLC1(c)        (BETWEEN(c, 0x80, 0x9f))
+ #define ISCONTROL(c)        (ISCONTROLC0(c) || ISCONTROLC1(c))
+ #define ISDELIM(u)        (u && wcschr(worddelimiters, u))
++#define TLINE(y)        ((y) < term.scr ? term.hist[((y) + term.histi - \
++                term.scr + HISTSIZE + 1) % HISTSIZE] : \
++                term.line[(y) - term.scr])
+ 
+ enum term_mode {
+     MODE_WRAP        = 1 << 0,
+@@ -115,6 +119,9 @@ typedef struct {
+     int col;      /* nb col */
+     Line *line;   /* screen */
+     Line *alt;    /* alternate screen */
++    Line hist[HISTSIZE]; /* history buffer */
++    int histi;    /* history index */
++    int scr;      /* scroll back */
+     int *dirty;   /* dirtyness of lines */
+     TCursor c;    /* cursor */
+     int ocx;      /* old cursor col */
+@@ -184,8 +191,8 @@ static void tnewline(int);
+ static void tputtab(int);
+ static void tputc(Rune);
+ static void treset(void);
+-static void tscrollup(int, int);
+-static void tscrolldown(int, int);
++static void tscrollup(int, int, int);
++static void tscrolldown(int, int, int);
+ static void tsetattr(const int *, int);
+ static void tsetchar(Rune, const Glyph *, int, int);
+ static void tsetdirt(int, int);
+@@ -416,10 +423,10 @@ tlinelen(int y)
+ {
+     int i = term.col;
+ 
+-    if (term.line[y][i - 1].mode & ATTR_WRAP)
++    if (TLINE(y)[i - 1].mode & ATTR_WRAP)
+         return i;
+ 
+-    while (i > 0 && term.line[y][i - 1].u == ' ')
++    while (i > 0 && TLINE(y)[i - 1].u == ' ')
+         --i;
+ 
+     return i;
+@@ -528,7 +535,7 @@ selsnap(int *x, int *y, int direction)
+          * Snap around if the word wraps around at the end or
+          * beginning of a line.
+          */
+-        prevgp = &term.line[*y][*x];
++        prevgp = &TLINE(*y)[*x];
+         prevdelim = ISDELIM(prevgp->u);
+         for (;;) {
+             newx = *x + direction;
+@@ -543,14 +550,14 @@ selsnap(int *x, int *y, int direction)
+                     yt = *y, xt = *x;
+                 else
+                     yt = newy, xt = newx;
+-                if (!(term.line[yt][xt].mode & ATTR_WRAP))
++                if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
+                     break;
+             }
+ 
+             if (newx >= tlinelen(newy))
+                 break;
+ 
+-            gp = &term.line[newy][newx];
++            gp = &TLINE(newy)[newx];
+             delim = ISDELIM(gp->u);
+             if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
+                     || (delim && gp->u != prevgp->u)))
+@@ -571,14 +578,14 @@ selsnap(int *x, int *y, int direction)
+         *x = (direction < 0) ? 0 : term.col - 1;
+         if (direction < 0) {
+             for (; *y > 0; *y += direction) {
+-                if (!(term.line[*y-1][term.col-1].mode
++                if (!(TLINE(*y-1)[term.col-1].mode
+                         & ATTR_WRAP)) {
+                     break;
+                 }
+             }
+         } else if (direction > 0) {
+             for (; *y < term.row-1; *y += direction) {
+-                if (!(term.line[*y][term.col-1].mode
++                if (!(TLINE(*y)[term.col-1].mode
+                         & ATTR_WRAP)) {
+                     break;
+                 }
+@@ -609,13 +616,13 @@ getsel(void)
+         }
+ 
+         if (sel.type == SEL_RECTANGULAR) {
+-            gp = &term.line[y][sel.nb.x];
++            gp = &TLINE(y)[sel.nb.x];
+             lastx = sel.ne.x;
+         } else {
+-            gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
++            gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
+             lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
+         }
+-        last = &term.line[y][MIN(lastx, linelen-1)];
++        last = &TLINE(y)[MIN(lastx, linelen-1)];
+         while (last >= gp && last->u == ' ')
+             --last;
+ 
+@@ -850,6 +857,9 @@ void
+ ttywrite(const char *s, size_t n, int may_echo)
+ {
+     const char *next;
++    Arg arg = (Arg) { .i = term.scr };
++
++    kscrolldown(&arg);
+ 
+     if (may_echo && IS_SET(MODE_ECHO))
+         twrite(s, n, 1);
+@@ -1061,13 +1071,53 @@ tswapscreen(void)
+ }
+ 
+ void
+-tscrolldown(int orig, int n)
++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();
++    }
++}
++
++void
++tscrolldown(int orig, int n, int copyhist)
+ {
+     int i;
+     Line temp;
+ 
+     LIMIT(n, 0, term.bot-orig+1);
+ 
++    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;
++    }
++
+     tsetdirt(orig, term.bot-n);
+     tclearregion(0, term.bot-n+1, term.col-1, term.bot);
+ 
+@@ -1077,17 +1127,28 @@ tscrolldown(int orig, int n)
+         term.line[i-n] = temp;
+     }
+ 
+-    selscroll(orig, n);
++    if (term.scr == 0)
++        selscroll(orig, n);
+ }
+ 
+ void
+-tscrollup(int orig, int n)
++tscrollup(int orig, int n, int copyhist)
+ {
+     int i;
+     Line temp;
+ 
+     LIMIT(n, 0, term.bot-orig+1);
+ 
++    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);
++
+     tclearregion(0, orig, term.col-1, orig+n-1);
+     tsetdirt(orig+n, term.bot);
+ 
+@@ -1097,7 +1158,8 @@ tscrollup(int orig, int n)
+         term.line[i+n] = temp;
+     }
+ 
+-    selscroll(orig, -n);
++    if (term.scr == 0)
++        selscroll(orig, -n);
+ }
+ 
+ void
+@@ -1126,7 +1188,7 @@ tnewline(int first_col)
+     int y = term.c.y;
+ 
+     if (y == term.bot) {
+-        tscrollup(term.top, 1);
++        tscrollup(term.top, 1, 1);
+     } else {
+         y++;
+     }
+@@ -1291,14 +1353,14 @@ void
+ tinsertblankline(int n)
+ {
+     if (BETWEEN(term.c.y, term.top, term.bot))
+-        tscrolldown(term.c.y, n);
++        tscrolldown(term.c.y, n, 0);
+ }
+ 
+ void
+ tdeleteline(int n)
+ {
+     if (BETWEEN(term.c.y, term.top, term.bot))
+-        tscrollup(term.c.y, n);
++        tscrollup(term.c.y, n, 0);
+ }
+ 
+ int32_t
+@@ -1735,11 +1797,11 @@ csihandle(void)
+         break;
+     case 'S': /* SU -- Scroll <n> line up */
+         DEFAULT(csiescseq.arg[0], 1);
+-        tscrollup(term.top, csiescseq.arg[0]);
++        tscrollup(term.top, csiescseq.arg[0], 0);
+         break;
+     case 'T': /* SD -- Scroll <n> line down */
+         DEFAULT(csiescseq.arg[0], 1);
+-        tscrolldown(term.top, csiescseq.arg[0]);
++        tscrolldown(term.top, csiescseq.arg[0], 0);
+         break;
+     case 'L': /* IL -- Insert <n> blank lines */
+         DEFAULT(csiescseq.arg[0], 1);
+@@ -2251,7 +2313,7 @@ eschandle(uchar ascii)
+         return 0;
+     case 'D': /* IND -- Linefeed */
+         if (term.c.y == term.bot) {
+-            tscrollup(term.top, 1);
++            tscrollup(term.top, 1, 1);
+         } else {
+             tmoveto(term.c.x, term.c.y+1);
+         }
+@@ -2264,7 +2326,7 @@ eschandle(uchar ascii)
+         break;
+     case 'M': /* RI -- Reverse index */
+         if (term.c.y == term.top) {
+-            tscrolldown(term.top, 1);
++            tscrolldown(term.top, 1, 1);
+         } else {
+             tmoveto(term.c.x, term.c.y-1);
+         }
+@@ -2474,7 +2536,7 @@ twrite(const char *buf, int buflen, int show_ctrl)
+ void
+ tresize(int col, int row)
+ {
+-    int i;
++    int i, j;
+     int minrow = MIN(row, term.row);
+     int mincol = MIN(col, term.col);
+     int *bp;
+@@ -2511,6 +2573,14 @@ tresize(int col, int row)
+     term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
+     term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
+ 
++    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 = ' ';
++        }
++    }
++
+     /* 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));
+@@ -2569,7 +2639,7 @@ drawregion(int x1, int y1, int x2, int y2)
+             continue;
+ 
+         term.dirty[y] = 0;
+-        xdrawline(term.line[y], x1, y, x2);
++        xdrawline(TLINE(y), x1, y, x2);
+     }
+ }
+ 
+@@ -2590,8 +2660,9 @@ draw(void)
+         cx--;
+ 
+     drawregion(0, 0, term.col, term.row);
+-    xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
+-            term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
++    if (term.scr == 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;
+     xfinishdraw();
+diff --git a/st.h b/st.h
+index fa2eddf..adda2db 100644
+--- a/st.h
++++ b/st.h
+@@ -81,6 +81,8 @@ void die(const char *, ...);
+ void redraw(void);
+ void draw(void);
+ 
++void kscrolldown(const Arg *);
++void kscrollup(const Arg *);
+ void printscreen(const Arg *);
+ void printsel(const Arg *);
+ void sendbreak(const Arg *);
diff --git a/patches/st-scrollback-mouse-20220127-2c5edf2.diff b/patches/st-scrollback-mouse-20220127-2c5edf2.diff
@@ -0,0 +1,25 @@
+From b5d3351a21442a842e01e8c0317603b6890b379c Mon Sep 17 00:00:00 2001
+From: asparagii <michele.lambertucci1@gmail.com>
+Date: Thu, 27 Jan 2022 15:44:02 +0100
+Subject: [PATCH] st-scrollback-mouse
+
+---
+ config.def.h | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/config.def.h b/config.def.h
+index e3b469b..c217315 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -176,6 +176,8 @@ static uint forcemousemod = ShiftMask;
+  */
+ static MouseShortcut mshortcuts[] = {
+     /* mask                 button   function        argument       release */
++    { ShiftMask,            Button4, kscrollup,      {.i = 1} },
++    { ShiftMask,            Button5, kscrolldown,    {.i = 1} },
+     { XK_ANY_MOD,           Button2, selpaste,       {.i = 0},      1 },
+     { ShiftMask,            Button4, ttysend,        {.s = "\033[5;2~"} },
+     { XK_ANY_MOD,           Button4, ttysend,        {.s = "\031"} },
+-- 
+2.34.1
+
diff --git a/patches/st-scrollback-reflow-0.8.5.diff b/patches/st-scrollback-reflow-0.8.5.diff
@@ -0,0 +1,1478 @@
+diff --git a/st.c b/st.c
+index 91e7077..a76d983 100644
+--- a/st.c
++++ b/st.c
+@@ -36,6 +36,7 @@
+ #define STR_BUF_SIZ   ESC_BUF_SIZ
+ #define STR_ARG_SIZ   ESC_ARG_SIZ
+ #define HISTSIZE      2000
++#define RESIZEBUFFER  1000
+ 
+ /* macros */
+ #define IS_SET(flag)        ((term.mode & (flag)) != 0)
+@@ -43,9 +44,22 @@
+ #define ISCONTROLC1(c)        (BETWEEN(c, 0x80, 0x9f))
+ #define ISCONTROL(c)        (ISCONTROLC0(c) || ISCONTROLC1(c))
+ #define ISDELIM(u)        (u && wcschr(worddelimiters, u))
+-#define TLINE(y)        ((y) < term.scr ? term.hist[((y) + term.histi - \
+-                term.scr + HISTSIZE + 1) % HISTSIZE] : \
+-                term.line[(y) - term.scr])
++
++#define TLINE(y) ( \
++    (y) < term.scr ? term.hist[(term.histi + (y) - term.scr + 1 + HISTSIZE) % HISTSIZE] \
++                   : term.line[(y) - term.scr] \
++)
++
++#define TLINEABS(y) ( \
++    (y) < 0 ? term.hist[(term.histi + (y) + 1 + HISTSIZE) % HISTSIZE] : term.line[(y)] \
++)
++
++#define UPDATEWRAPNEXT(alt, col) do { \
++    if ((term.c.state & CURSOR_WRAPNEXT) && term.c.x + term.wrapcwidth[alt] < col) { \
++        term.c.x += term.wrapcwidth[alt]; \
++        term.c.state &= ~CURSOR_WRAPNEXT; \
++    } \
++} while (0);
+ 
+ enum term_mode {
+     MODE_WRAP        = 1 << 0,
+@@ -57,6 +71,12 @@ enum term_mode {
+     MODE_UTF8        = 1 << 6,
+ };
+ 
++enum scroll_mode {
++    SCROLL_RESIZE = -1,
++    SCROLL_NOSAVEHIST = 0,
++    SCROLL_SAVEHIST = 1
++};
++
+ enum cursor_movement {
+     CURSOR_SAVE,
+     CURSOR_LOAD
+@@ -118,10 +138,11 @@ typedef struct {
+     int row;      /* nb row */
+     int col;      /* nb col */
+     Line *line;   /* screen */
+-    Line *alt;    /* alternate screen */
+     Line hist[HISTSIZE]; /* history buffer */
+-    int histi;    /* history index */
+-    int scr;      /* scroll back */
++    int histi;           /* history index */
++    int histf;           /* nb history available */
++    int scr;             /* scroll back */
++    int wrapcwidth[2];   /* used in updating WRAPNEXT when resizing */
+     int *dirty;   /* dirtyness of lines */
+     TCursor c;    /* cursor */
+     int ocx;      /* old cursor col */
+@@ -179,26 +200,37 @@ static void tprinter(char *, size_t);
+ static void tdumpsel(void);
+ static void tdumpline(int);
+ static void tdump(void);
+-static void tclearregion(int, int, int, int);
++static void tclearregion(int, int, int, int, int);
+ static void tcursor(int);
++static void tclearglyph(Glyph *, int);
++static void tresetcursor(void);
+ static void tdeletechar(int);
+ static void tdeleteline(int);
+ static void tinsertblank(int);
+ static void tinsertblankline(int);
+-static int tlinelen(int);
++static int tlinelen(Line len);
++static int tiswrapped(Line line);
++static char *tgetglyphs(char *, const Glyph *, const Glyph *);
++static size_t tgetline(char *, const Glyph *);
+ static void tmoveto(int, int);
+ static void tmoveato(int, int);
+ static void tnewline(int);
+ static void tputtab(int);
+ static void tputc(Rune);
+ static void treset(void);
+-static void tscrollup(int, int, int);
+-static void tscrolldown(int, int, int);
++static void tscrollup(int, int, int, int);
++static void tscrolldown(int, int);
++static void treflow(int, int);
++static void rscrolldown(int);
++static void tresizedef(int, int);
++static void tresizealt(int, int);
+ static void tsetattr(const int *, int);
+ static void tsetchar(Rune, const Glyph *, int, int);
+ static void tsetdirt(int, int);
+ static void tsetscroll(int, int);
+ static void tswapscreen(void);
++static void tloaddefscreen(int, int);
++static void tloadaltscreen(int, int);
+ static void tsetmode(int, int, const int *, int);
+ static int twrite(const char *, int, int);
+ static void tfulldirt(void);
+@@ -212,7 +244,10 @@ static void tstrsequence(uchar);
+ static void drawregion(int, int, int, int);
+ 
+ static void selnormalize(void);
+-static void selscroll(int, int);
++static void selscroll(int, int, int);
++static void selmove(int);
++static void selremove(void);
++static int regionselected(int, int, int, int);
+ static void selsnap(int *, int *, int);
+ 
+ static size_t utf8decode(const char *, Rune *, size_t);
+@@ -412,17 +447,46 @@ selinit(void)
+ }
+ 
+ int
+-tlinelen(int y)
++tlinelen(Line line)
+ {
+-    int i = term.col;
++    int i = term.col - 1;
++
++    for (; i >= 0 && !(line[i].mode & (ATTR_SET | ATTR_WRAP)); i--);
++    return i + 1;
++}
+ 
+-    if (TLINE(y)[i - 1].mode & ATTR_WRAP)
+-        return i;
++int
++tiswrapped(Line line)
++{
++    int len = tlinelen(line);
+ 
+-    while (i > 0 && TLINE(y)[i - 1].u == ' ')
+-        --i;
++    return len > 0 && (line[len - 1].mode & ATTR_WRAP);
++}
+ 
+-    return i;
++char *
++tgetglyphs(char *buf, const Glyph *gp, const Glyph *lgp)
++{
++    while (gp <= lgp)
++        if (gp->mode & ATTR_WDUMMY) {
++            gp++;
++        } else {
++            buf += utf8encode((gp++)->u, buf);
++        }
++    return buf;
++}
++
++size_t
++tgetline(char *buf, const Glyph *fgp)
++{
++    char *ptr;
++    const Glyph *lgp = &fgp[term.col - 1];
++
++    while (lgp > fgp && !(lgp->mode & (ATTR_SET | ATTR_WRAP)))
++        lgp--;
++    ptr = tgetglyphs(buf, fgp, lgp);
++    if (!(lgp->mode & ATTR_WRAP))
++        *(ptr++) = '\n';
++    return ptr - buf;
+ }
+ 
+ void
+@@ -462,10 +526,11 @@ selextend(int col, int row, int type, int done)
+ 
+     sel.oe.x = col;
+     sel.oe.y = row;
+-    selnormalize();
+     sel.type = type;
++    selnormalize();
+ 
+-    if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
++    if (oldey != sel.oe.y || oldex != sel.oe.x ||
++        oldtype != sel.type || sel.mode == SEL_EMPTY)
+         tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
+ 
+     sel.mode = done ? SEL_IDLE : SEL_READY;
+@@ -492,36 +557,43 @@ selnormalize(void)
+     /* expand selection over line breaks */
+     if (sel.type == SEL_RECTANGULAR)
+         return;
+-    i = tlinelen(sel.nb.y);
+-    if (i < sel.nb.x)
++
++  i = tlinelen(TLINE(sel.nb.y));
++    if (sel.nb.x > i)
+         sel.nb.x = i;
+-    if (tlinelen(sel.ne.y) <= sel.ne.x)
+-        sel.ne.x = term.col - 1;
++  if (sel.ne.x >= tlinelen(TLINE(sel.ne.y)))
++    sel.ne.x = term.col - 1;
+ }
+ 
+ int
+-selected(int x, int y)
++regionselected(int x1, int y1, int x2, int y2)
+ {
+-    if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
+-            sel.alt != IS_SET(MODE_ALTSCREEN))
++    if (sel.ob.x == -1 || sel.mode == SEL_EMPTY ||
++        sel.alt != IS_SET(MODE_ALTSCREEN) || sel.nb.y > y2 || sel.ne.y < y1)
+         return 0;
+ 
+-    if (sel.type == SEL_RECTANGULAR)
+-        return BETWEEN(y, sel.nb.y, sel.ne.y)
+-            && BETWEEN(x, sel.nb.x, sel.ne.x);
++    return (sel.type == SEL_RECTANGULAR) ? sel.nb.x <= x2 && sel.ne.x >= x1
++        : (sel.nb.y != y2 || sel.nb.x <= x2) &&
++          (sel.ne.y != y1 || sel.ne.x >= x1);
++}
+ 
+-    return BETWEEN(y, sel.nb.y, sel.ne.y)
+-        && (y != sel.nb.y || x >= sel.nb.x)
+-        && (y != sel.ne.y || x <= sel.ne.x);
++int
++selected(int x, int y)
++{
++    return regionselected(x, y, x, y);
+ }
+ 
+ void
+ selsnap(int *x, int *y, int direction)
+ {
+     int newx, newy, xt, yt;
++    int rtop = 0, rbot = term.row - 1;
+     int delim, prevdelim;
+     const Glyph *gp, *prevgp;
+ 
++    if (!IS_SET(MODE_ALTSCREEN))
++        rtop += -term.histf + term.scr, rbot += term.scr;
++
+     switch (sel.snap) {
+     case SNAP_WORD:
+         /*
+@@ -536,7 +608,7 @@ selsnap(int *x, int *y, int direction)
+             if (!BETWEEN(newx, 0, term.col - 1)) {
+                 newy += direction;
+                 newx = (newx + term.col) % term.col;
+-                if (!BETWEEN(newy, 0, term.row - 1))
++                if (!BETWEEN(newy, rtop, rbot))
+                     break;
+ 
+                 if (direction > 0)
+@@ -547,13 +619,13 @@ selsnap(int *x, int *y, int direction)
+                     break;
+             }
+ 
+-            if (newx >= tlinelen(newy))
++            if (newx >= tlinelen(TLINE(newy)))
+                 break;
+ 
+             gp = &TLINE(newy)[newx];
+             delim = ISDELIM(gp->u);
+-            if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
+-                    || (delim && gp->u != prevgp->u)))
++            if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim ||
++                (delim && !(gp->u == ' ' && prevgp->u == ' '))))
+                 break;
+ 
+             *x = newx;
+@@ -570,18 +642,14 @@ selsnap(int *x, int *y, int direction)
+          */
+         *x = (direction < 0) ? 0 : term.col - 1;
+         if (direction < 0) {
+-            for (; *y > 0; *y += direction) {
+-                if (!(TLINE(*y-1)[term.col-1].mode
+-                        & ATTR_WRAP)) {
++            for (; *y > rtop; *y -= 1) {
++                if (!tiswrapped(TLINE(*y-1)))
+                     break;
+-                }
+             }
+         } else if (direction > 0) {
+-            for (; *y < term.row-1; *y += direction) {
+-                if (!(TLINE(*y)[term.col-1].mode
+-                        & ATTR_WRAP)) {
++            for (; *y < rbot; *y += 1) {
++                if (!tiswrapped(TLINE(*y)))
+                     break;
+-                }
+             }
+         }
+         break;
+@@ -592,40 +660,34 @@ char *
+ getsel(void)
+ {
+     char *str, *ptr;
+-    int y, bufsize, lastx, linelen;
+-    const Glyph *gp, *last;
++    int y, lastx, linelen;
++    const Glyph *gp, *lgp;
+ 
+-    if (sel.ob.x == -1)
++    if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
+         return NULL;
+ 
+-    bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
+-    ptr = str = xmalloc(bufsize);
++    str = xmalloc((term.col + 1) * (sel.ne.y - sel.nb.y + 1) * UTF_SIZ);
++    ptr = str;
+ 
+     /* append every set & selected glyph to the selection */
+     for (y = sel.nb.y; y <= sel.ne.y; y++) {
+-        if ((linelen = tlinelen(y)) == 0) {
++        Line line = TLINE(y);
++
++        if ((linelen = tlinelen(line)) == 0) {
+             *ptr++ = '\n';
+             continue;
+         }
+ 
+         if (sel.type == SEL_RECTANGULAR) {
+-            gp = &TLINE(y)[sel.nb.x];
++            gp = &line[sel.nb.x];
+             lastx = sel.ne.x;
+         } else {
+-            gp = &TLINE(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 = &TLINE(y)[MIN(lastx, linelen-1)];
+-        while (last >= gp && last->u == ' ')
+-            --last;
+-
+-        for ( ; gp <= last; ++gp) {
+-            if (gp->mode & ATTR_WDUMMY)
+-                continue;
+-
+-            ptr += utf8encode(gp->u, ptr);
+-        }
++        lgp = &line[MIN(lastx, linelen-1)];
+ 
++        ptr = tgetglyphs(ptr, gp, lgp);
+         /*
+          * Copy and pasting of line endings is inconsistent
+          * in the inconsistent terminal and GUI world.
+@@ -636,10 +698,10 @@ getsel(void)
+          * FIXME: Fix the computer world.
+          */
+         if ((y < sel.ne.y || lastx >= linelen) &&
+-            (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
++            (!(lgp->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
+             *ptr++ = '\n';
+     }
+-    *ptr = 0;
++    *ptr = '\0';
+     return str;
+ }
+ 
+@@ -648,9 +710,15 @@ selclear(void)
+ {
+     if (sel.ob.x == -1)
+         return;
++    selremove();
++    tsetdirt(sel.nb.y, sel.ne.y);
++}
++
++void
++selremove(void)
++{
+     sel.mode = SEL_IDLE;
+     sel.ob.x = -1;
+-    tsetdirt(sel.nb.y, sel.ne.y);
+ }
+ 
+ void
+@@ -851,10 +919,8 @@ void
+ ttywrite(const char *s, size_t n, int may_echo)
+ {
+     const char *next;
+-    Arg arg = (Arg) { .i = term.scr };
+-
+-    kscrolldown(&arg);
+ 
++    kscrolldown(&((Arg){ .i = term.scr }));
+     if (may_echo && IS_SET(MODE_ECHO))
+         twrite(s, n, 1);
+ 
+@@ -990,7 +1056,7 @@ tsetdirtattr(int attr)
+     for (i = 0; i < term.row-1; i++) {
+         for (j = 0; j < term.col-1; j++) {
+             if (term.line[i][j].mode & attr) {
+-                tsetdirt(i, i);
++                term.dirty[i] = 1;
+                 break;
+             }
+         }
+@@ -1000,7 +1066,8 @@ tsetdirtattr(int attr)
+ void
+ tfulldirt(void)
+ {
+-    tsetdirt(0, term.row-1);
++  for (int i = 0; i < term.row; i++)
++        term.dirty[i] = 1;
+ }
+ 
+ void
+@@ -1017,51 +1084,116 @@ tcursor(int mode)
+     }
+ }
+ 
++void
++tresetcursor(void)
++{
++    term.c = (TCursor){ { .mode = ATTR_NULL, .fg = defaultfg, .bg = defaultbg },
++                        .x = 0, .y = 0, .state = CURSOR_DEFAULT };
++}
++
+ void
+ treset(void)
+ {
+     uint i;
++  int x, y;
+ 
+-    term.c = (TCursor){{
+-        .mode = ATTR_NULL,
+-        .fg = defaultfg,
+-        .bg = defaultbg
+-    }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
++    tresetcursor();
+ 
+     memset(term.tabs, 0, term.col * sizeof(*term.tabs));
+     for (i = tabspaces; i < term.col; i += tabspaces)
+         term.tabs[i] = 1;
+     term.top = 0;
++    term.histf = 0;
++    term.scr = 0;
+     term.bot = term.row - 1;
+     term.mode = MODE_WRAP|MODE_UTF8;
+     memset(term.trantbl, CS_USA, sizeof(term.trantbl));
+     term.charset = 0;
+ 
++  selremove();
+     for (i = 0; i < 2; i++) {
+-        tmoveto(0, 0);
+-        tcursor(CURSOR_SAVE);
+-        tclearregion(0, 0, term.col-1, term.row-1);
++      tcursor(CURSOR_SAVE); /* reset saved cursor */
++        for (y = 0; y < term.row; y++)
++            for (x = 0; x < term.col; x++)
++                tclearglyph(&term.line[y][x], 0);
+         tswapscreen();
+     }
++  tfulldirt();
+ }
+ 
+ void
+ tnew(int col, int row)
+ {
+-    term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
+-    tresize(col, row);
+-    treset();
++    int i, j;
++
++    for (i = 0; i < 2; i++) {
++        term.line = xmalloc(row * sizeof(Line));
++        for (j = 0; j < row; j++)
++            term.line[j] = xmalloc(col * sizeof(Glyph));
++        term.col = col, term.row = row;
++        tswapscreen();
++    }
++    term.dirty = xmalloc(row * sizeof(*term.dirty));
++    term.tabs = xmalloc(col * sizeof(*term.tabs));
++    for (i = 0; i < HISTSIZE; i++)
++        term.hist[i] = xmalloc(col * sizeof(Glyph));
++  treset();
+ }
+ 
++/* handle it with care */
+ void
+ tswapscreen(void)
+ {
+-    Line *tmp = term.line;
++    static Line *altline;
++    static int altcol, altrow;
++    Line *tmpline = term.line;
++    int tmpcol = term.col, tmprow = term.row;
+ 
+-    term.line = term.alt;
+-    term.alt = tmp;
++    term.line = altline;
++    term.col = altcol, term.row = altrow;
++    altline = tmpline;
++    altcol = tmpcol, altrow = tmprow;
+     term.mode ^= MODE_ALTSCREEN;
+-    tfulldirt();
++}
++
++void
++tloaddefscreen(int clear, int loadcursor)
++{
++    int col, row, alt = IS_SET(MODE_ALTSCREEN);
++
++    if (alt) {
++        if (clear)
++            tclearregion(0, 0, term.col-1, term.row-1, 1);
++        col = term.col, row = term.row;
++        tswapscreen();
++    }
++    if (loadcursor)
++        tcursor(CURSOR_LOAD);
++    if (alt)
++        tresizedef(col, row);
++}
++
++void
++tloadaltscreen(int clear, int savecursor)
++{
++    int col, row, def = !IS_SET(MODE_ALTSCREEN);
++
++    if (savecursor)
++        tcursor(CURSOR_SAVE);
++    if (def) {
++        col = term.col, row = term.row;
++        tswapscreen();
++        term.scr = 0;
++        tresizealt(col, row);
++    }
++    if (clear)
++        tclearregion(0, 0, term.col-1, term.row-1, 1);
++}
++
++int
++tisaltscreen(void)
++{
++    return IS_SET(MODE_ALTSCREEN);
+ }
+ 
+ void
+@@ -1069,17 +1201,22 @@ kscrolldown(const Arg* a)
+ {
+     int n = a->i;
+ 
+-    if (n < 0)
+-        n = term.row + n;
++    if (!term.scr || IS_SET(MODE_ALTSCREEN))
++        return;
+ 
+-    if (n > term.scr)
+-        n = term.scr;
++    if (n < 0)
++        n = MAX(term.row / -n, 1);
+ 
+-    if (term.scr > 0) {
++    if (n <= term.scr) {
+         term.scr -= n;
+-        selscroll(0, -n);
+-        tfulldirt();
++    } else {
++        n = term.scr;
++        term.scr = 0;
+     }
++
++    if (sel.ob.x != -1 && !sel.alt)
++        selmove(-n); /* negate change in term.scr */
++    tfulldirt();
+ }
+ 
+ void
+@@ -1087,92 +1224,118 @@ kscrollup(const Arg* a)
+ {
+     int n = a->i;
+ 
++    if (!term.histf || IS_SET(MODE_ALTSCREEN))
++        return;
++
+     if (n < 0)
+-        n = term.row + n;
++        n = MAX(term.row / -n, 1);
+ 
+-    if (term.scr <= HISTSIZE-n) {
++    if (term.scr + n <= term.histf) {
+         term.scr += n;
+-        selscroll(0, n);
+-        tfulldirt();
++    } else {
++        n = term.histf - term.scr;
++        term.scr = term.histf;
+     }
++
++    if (sel.ob.x != -1 && !sel.alt)
++        selmove(n); /* negate change in term.scr */
++    tfulldirt();
+ }
+ 
+ void
+-tscrolldown(int orig, int n, int copyhist)
++tscrolldown(int top, int n)
+ {
+-    int i;
++    int i, bot = term.bot;
+     Line temp;
+ 
+-    LIMIT(n, 0, term.bot-orig+1);
+-    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;
+-    }
+-
++    if (n <= 0)
++        return;
++    n = MIN(n, bot-top+1);
+ 
+-    tsetdirt(orig, term.bot-n);
+-    tclearregion(0, term.bot-n+1, term.col-1, term.bot);
++    tsetdirt(top, bot-n);
++    tclearregion(0, bot-n+1, term.col-1, bot, 1);
+ 
+-    for (i = term.bot; i >= orig+n; i--) {
++    for (i = bot; i >= top+n; i--) {
+         temp = term.line[i];
+         term.line[i] = term.line[i-n];
+         term.line[i-n] = temp;
+     }
+ 
+-    if (term.scr == 0)
+-        selscroll(orig, n);
++    if (sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN))
++        selscroll(top, bot, n);
+ }
+ 
+ void
+-tscrollup(int orig, int n, int copyhist)
++tscrollup(int top, int bot, int n, int mode)
+ {
+-    int i;
++    int i, j, s;
++    int alt = IS_SET(MODE_ALTSCREEN);
++    int savehist = !alt && top == 0 && mode != SCROLL_NOSAVEHIST;
+     Line temp;
+ 
+-    LIMIT(n, 0, term.bot-orig+1);
+-
+-    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 (n <= 0)
++        return;
++    n = MIN(n, bot-top+1);
++
++    if (savehist) {
++        for (i = 0; i < n; i++) {
++            term.histi = (term.histi + 1) % HISTSIZE;
++            temp = term.hist[term.histi];
++            for (j = 0; j < term.col; j++)
++                tclearglyph(&temp[j], 1);
++            term.hist[term.histi] = term.line[i];
++            term.line[i] = temp;
++        }
++        term.histf = MIN(term.histf + n, HISTSIZE);
++        s = n;
++        if (term.scr) {
++            j = term.scr;
++            term.scr = MIN(j + n, HISTSIZE);
++            s = j + n - term.scr;
++        }
++        if (mode != SCROLL_RESIZE)
++            tfulldirt();
++    } else {
++        tclearregion(0, top, term.col-1, top+n-1, 1);
++        tsetdirt(top+n, bot);
+     }
+ 
+-    if (term.scr > 0 && term.scr < HISTSIZE)
+-        term.scr = MIN(term.scr + n, HISTSIZE-1);
+-
+-    tclearregion(0, orig, term.col-1, orig+n-1);
+-    tsetdirt(orig+n, term.bot);
+-
+-    for (i = orig; i <= term.bot-n; i++) {
++    for (i = top; i <= bot-n; i++) {
+         temp = term.line[i];
+         term.line[i] = term.line[i+n];
+         term.line[i+n] = temp;
+     }
+ 
+-    if (term.scr == 0)
+-        selscroll(orig, -n);
++    if (sel.ob.x != -1 && sel.alt == alt) {
++        if (!savehist) {
++            selscroll(top, bot, -n);
++        } else if (s > 0) {
++            selmove(-s);
++            if (-term.scr + sel.nb.y < -term.histf)
++                selremove();
++        }
++    }
+ }
+ 
+ void
+-selscroll(int orig, int n)
++selmove(int n)
+ {
+-    if (sel.ob.x == -1)
+-        return;
++    sel.ob.y += n, sel.nb.y += n;
++    sel.oe.y += n, sel.ne.y += n;
++}
++
++void
++selscroll(int top, int bot, int n)
++{
++    /* turn absolute coordinates into relative */
++    top += term.scr, bot += term.scr;
+ 
+-    if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
++    if (BETWEEN(sel.nb.y, top, bot) != BETWEEN(sel.ne.y, top, bot)) {
+         selclear();
+-    } 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) {
++    } else if (BETWEEN(sel.nb.y, top, bot)) {
++        selmove(n);
++        if (sel.nb.y < top || sel.ne.y > bot)
+             selclear();
+-        } else {
+-            selnormalize();
+-        }
+     }
+ }
+ 
+@@ -1182,7 +1345,7 @@ tnewline(int first_col)
+     int y = term.c.y;
+ 
+     if (y == term.bot) {
+-        tscrollup(term.top, 1, 1);
++        tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST);
+     } else {
+         y++;
+     }
+@@ -1272,89 +1435,93 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
+     } else if (term.line[y][x].mode & ATTR_WDUMMY) {
+         term.line[y][x-1].u = ' ';
+         term.line[y][x-1].mode &= ~ATTR_WIDE;
+-    }
++  }
+ 
+     term.dirty[y] = 1;
+     term.line[y][x] = *attr;
+     term.line[y][x].u = u;
++    term.line[y][x].mode |= ATTR_SET;
+ }
+ 
+ void
+-tclearregion(int x1, int y1, int x2, int y2)
++tclearglyph(Glyph *gp, int usecurattr)
+ {
+-    int x, y, temp;
+-    Glyph *gp;
++    if (usecurattr) {
++        gp->fg = term.c.attr.fg;
++        gp->bg = term.c.attr.bg;
++    } else {
++        gp->fg = defaultfg;
++        gp->bg = defaultbg;
++    }
++    gp->mode = ATTR_NULL;
++    gp->u = ' ';
++}
+ 
+-    if (x1 > x2)
+-        temp = x1, x1 = x2, x2 = temp;
+-    if (y1 > y2)
+-        temp = y1, y1 = y2, y2 = temp;
++void
++tclearregion(int x1, int y1, int x2, int y2, int usecurattr)
++{
++    int x, y;
+ 
+-    LIMIT(x1, 0, term.col-1);
+-    LIMIT(x2, 0, term.col-1);
+-    LIMIT(y1, 0, term.row-1);
+-    LIMIT(y2, 0, term.row-1);
++    /* regionselected() takes relative coordinates */
++    if (regionselected(x1+term.scr, y1+term.scr, x2+term.scr, y2+term.scr))
++        selremove();
+ 
+     for (y = y1; y <= y2; y++) {
+         term.dirty[y] = 1;
+-        for (x = x1; x <= x2; x++) {
+-            gp = &term.line[y][x];
+-            if (selected(x, y))
+-                selclear();
+-            gp->fg = term.c.attr.fg;
+-            gp->bg = term.c.attr.bg;
+-            gp->mode = 0;
+-            gp->u = ' ';
+-        }
++        for (x = x1; x <= x2; x++)
++            tclearglyph(&term.line[y][x], usecurattr);
+     }
+ }
+ 
+ void
+ tdeletechar(int n)
+ {
+-    int dst, src, size;
+-    Glyph *line;
+-
+-    LIMIT(n, 0, term.col - term.c.x);
++    int src, dst, size;
++    Line line;
+ 
++    if (n <= 0)
++        return;
+     dst = term.c.x;
+-    src = term.c.x + n;
++    src = MIN(term.c.x + n, term.col);
+     size = term.col - src;
+-    line = term.line[term.c.y];
+-
+-    memmove(&line[dst], &line[src], size * sizeof(Glyph));
+-    tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
++    if (size > 0) { /* otherwise src would point beyond the array
++                       https://stackoverflow.com/questions/29844298 */
++        line = term.line[term.c.y];
++        memmove(&line[dst], &line[src], size * sizeof(Glyph));
++    }
++    tclearregion(dst + size, term.c.y, term.col - 1, term.c.y, 1);
+ }
+ 
+ void
+ tinsertblank(int n)
+ {
+-    int dst, src, size;
+-    Glyph *line;
++    int src, dst, size;
++    Line line;
+ 
+-    LIMIT(n, 0, term.col - term.c.x);
+-
+-    dst = term.c.x + n;
++    if (n <= 0)
++        return;
++    dst = MIN(term.c.x + n, term.col);
+     src = term.c.x;
+     size = term.col - dst;
+-    line = term.line[term.c.y];
+-
+-    memmove(&line[dst], &line[src], size * sizeof(Glyph));
+-    tclearregion(src, term.c.y, dst - 1, term.c.y);
++    if (size > 0) { /* otherwise dst would point beyond the array */
++        line = term.line[term.c.y];
++        memmove(&line[dst], &line[src], size * sizeof(Glyph));
++    }
++    tclearregion(src, term.c.y, dst - 1, term.c.y, 1);
+ }
+ 
+ void
+ tinsertblankline(int n)
+ {
+     if (BETWEEN(term.c.y, term.top, term.bot))
+-        tscrolldown(term.c.y, n, 0);
++        tscrolldown(term.c.y, n);
+ }
+ 
+ void
+ tdeleteline(int n)
+ {
+     if (BETWEEN(term.c.y, term.top, term.bot))
+-        tscrollup(term.c.y, n, 0);
++        tscrollup(term.c.y, term.bot, n, SCROLL_NOSAVEHIST);
+ }
+ 
+ int32_t
+@@ -1528,7 +1695,7 @@ tsetscroll(int t, int b)
+ void
+ tsetmode(int priv, int set, const int *args, int narg)
+ {
+-    int alt; const int *lim;
++    const int *lim;
+ 
+     for (lim = args + narg; args < lim; ++args) {
+         if (priv) {
+@@ -1589,25 +1756,18 @@ tsetmode(int priv, int set, const int *args, int narg)
+                 xsetmode(set, MODE_8BIT);
+                 break;
+             case 1049: /* swap screen & set/restore cursor as xterm */
+-                if (!allowaltscreen)
+-                    break;
+-                tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
+-                /* FALLTHROUGH */
+             case 47: /* swap screen */
+-            case 1047:
++            case 1047: /* swap screen, clearing alternate screen */
+                 if (!allowaltscreen)
+                     break;
+-                alt = IS_SET(MODE_ALTSCREEN);
+-                if (alt) {
+-                    tclearregion(0, 0, term.col-1,
+-                            term.row-1);
+-                }
+-                if (set ^ alt) /* set is always 1 or 0 */
+-                    tswapscreen();
+-                if (*args != 1049)
+-                    break;
+-                /* FALLTHROUGH */
++                if (set)
++                    tloadaltscreen(*args == 1049, *args == 1049);
++                else
++                    tloaddefscreen(*args == 1047, *args == 1049);
++                break;
+             case 1048:
++                if (!allowaltscreen)
++          break;
+                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
+                 break;
+             case 2004: /* 2004: bracketed paste mode */
+@@ -1659,7 +1819,7 @@ void
+ csihandle(void)
+ {
+     char buf[40];
+-    int len;
++    int n, x;
+ 
+     switch (csiescseq.mode[0]) {
+     default:
+@@ -1757,20 +1917,30 @@ csihandle(void)
+     case 'J': /* ED -- Clear screen */
+         switch (csiescseq.arg[0]) {
+         case 0: /* below */
+-            tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
++            tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1);
+             if (term.c.y < term.row-1) {
+-                tclearregion(0, term.c.y+1, term.col-1,
+-                        term.row-1);
++                tclearregion(0, term.c.y+1, term.col-1, term.row-1, 1);
+             }
+             break;
+         case 1: /* above */
+-            if (term.c.y > 1)
+-                tclearregion(0, 0, term.col-1, term.c.y-1);
+-            tclearregion(0, term.c.y, term.c.x, term.c.y);
++            if (term.c.y >= 1)
++                tclearregion(0, 0, term.col-1, term.c.y-1, 1);
++            tclearregion(0, term.c.y, term.c.x, term.c.y, 1);
+             break;
+         case 2: /* all */
+-            tclearregion(0, 0, term.col-1, term.row-1);
+-            break;
++            if (IS_SET(MODE_ALTSCREEN)) {
++              tclearregion(0, 0, term.col-1, term.row-1, 1);
++              break;
++      }
++            /* vte does this:
++            tscrollup(0, term.row-1, term.row, SCROLL_SAVEHIST); */
++      
++            /* alacritty does this: */
++            for (n = term.row-1; n >= 0 && tlinelen(term.line[n]) == 0; n--);
++            if (n >= 0)
++                tscrollup(0, term.row-1, n+1, SCROLL_SAVEHIST);
++            tscrollup(0, term.row-1, term.row-n-1, SCROLL_NOSAVEHIST);
++      break;
+         default:
+             goto unknown;
+         }
+@@ -1778,24 +1948,24 @@ csihandle(void)
+     case 'K': /* EL -- Clear line */
+         switch (csiescseq.arg[0]) {
+         case 0: /* right */
+-            tclearregion(term.c.x, term.c.y, term.col-1,
+-                    term.c.y);
++            tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1);
+             break;
+         case 1: /* left */
+-            tclearregion(0, term.c.y, term.c.x, term.c.y);
++            tclearregion(0, term.c.y, term.c.x, term.c.y, 1);
+             break;
+         case 2: /* all */
+-            tclearregion(0, term.c.y, term.col-1, term.c.y);
++            tclearregion(0, term.c.y, term.col-1, term.c.y, 1);
+             break;
+         }
+         break;
+     case 'S': /* SU -- Scroll <n> line up */
+         DEFAULT(csiescseq.arg[0], 1);
+-        tscrollup(term.top, csiescseq.arg[0], 0);
++        /* xterm, urxvt, alacritty save this in history */
++        tscrollup(term.top, term.bot, csiescseq.arg[0], SCROLL_SAVEHIST);
+         break;
+     case 'T': /* SD -- Scroll <n> line down */
+         DEFAULT(csiescseq.arg[0], 1);
+-        tscrolldown(term.top, csiescseq.arg[0], 0);
++        tscrolldown(term.top, csiescseq.arg[0]);
+         break;
+     case 'L': /* IL -- Insert <n> blank lines */
+         DEFAULT(csiescseq.arg[0], 1);
+@@ -1809,9 +1979,11 @@ csihandle(void)
+         tdeleteline(csiescseq.arg[0]);
+         break;
+     case 'X': /* ECH -- Erase <n> char */
++        if (csiescseq.arg[0] < 0)
++            return;
+         DEFAULT(csiescseq.arg[0], 1);
+-        tclearregion(term.c.x, term.c.y,
+-                term.c.x + csiescseq.arg[0] - 1, term.c.y);
++        x = MIN(term.c.x + csiescseq.arg[0], term.col) - 1;
++        tclearregion(term.c.x, term.c.y, x, term.c.y, 1);
+         break;
+     case 'P': /* DCH -- Delete <n> char */
+         DEFAULT(csiescseq.arg[0], 1);
+@@ -1833,9 +2005,9 @@ csihandle(void)
+         break;
+     case 'n': /* DSR – Device Status Report (cursor position) */
+         if (csiescseq.arg[0] == 6) {
+-            len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
++            n = snprintf(buf, sizeof(buf), "\033[%i;%iR",
+                     term.c.y+1, term.c.x+1);
+-            ttywrite(buf, len, 0);
++            ttywrite(buf, n, 0);
+         }
+         break;
+     case 'r': /* DECSTBM -- Set Scrolling Region */
+@@ -2128,16 +2300,8 @@ tdumpsel(void)
+ void
+ tdumpline(int n)
+ {
+-    char buf[UTF_SIZ];
+-    const Glyph *bp, *end;
+-
+-    bp = &term.line[n][0];
+-    end = &bp[MIN(tlinelen(n), term.col) - 1];
+-    if (bp != end || bp->u != ' ') {
+-        for ( ; bp <= end; ++bp)
+-            tprinter(buf, utf8encode(bp->u, buf));
+-    }
+-    tprinter("\n", 1);
++    char str[(term.col + 1) * UTF_SIZ];
++  tprinter(str, tgetline(str, &term.line[n][0]));
+ }
+ 
+ void
+@@ -2358,7 +2522,7 @@ eschandle(uchar ascii)
+         return 0;
+     case 'D': /* IND -- Linefeed */
+         if (term.c.y == term.bot) {
+-            tscrollup(term.top, 1, 1);
++            tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST);
+         } else {
+             tmoveto(term.c.x, term.c.y+1);
+         }
+@@ -2371,7 +2535,7 @@ eschandle(uchar ascii)
+         break;
+     case 'M': /* RI -- Reverse index */
+         if (term.c.y == term.top) {
+-            tscrolldown(term.top, 1, 1);
++            tscrolldown(term.top, 1);
+         } else {
+             tmoveto(term.c.x, term.c.y-1);
+         }
+@@ -2511,7 +2675,8 @@ check_control_code:
+          */
+         return;
+     }
+-    if (selected(term.c.x, term.c.y))
++    /* selected() takes relative coordinates */
++    if (selected(term.c.x + term.scr, term.c.y + term.scr))
+         selclear();
+ 
+     gp = &term.line[term.c.y][term.c.x];
+@@ -2546,6 +2711,7 @@ check_control_code:
+     if (term.c.x+width < term.col) {
+         tmoveto(term.c.x+width, term.c.y);
+     } else {
++        term.wrapcwidth[IS_SET(MODE_ALTSCREEN)] = width;
+         term.c.state |= CURSOR_WRAPNEXT;
+     }
+ }
+@@ -2583,93 +2749,275 @@ twrite(const char *buf, int buflen, int show_ctrl)
+ }
+ 
+ void
+-tresize(int col, int row)
++treflow(int col, int row)
+ {
+     int i, j;
+-    int minrow = MIN(row, term.row);
+-    int mincol = MIN(col, term.col);
+-    int *bp;
+-    TCursor c;
+-
+-    if (col < 1 || row < 1) {
+-        fprintf(stderr,
+-                "tresize: error resizing to %dx%d\n", col, row);
+-        return;
++    int oce, nce, bot, scr;
++    int ox = 0, oy = -term.histf, nx = 0, ny = -1, len;
++    int cy = -1; /* proxy for new y coordinate of cursor */
++    int nlines;
++    Line *buf, line;
++
++    /* y coordinate of cursor line end */
++    for (oce = term.c.y; oce < term.row - 1 &&
++                         tiswrapped(term.line[oce]); oce++);
++
++    nlines = term.histf + oce + 1;
++    if (col < term.col) {
++        /* each line can take this many lines after reflow */
++        j = (term.col + col - 1) / col;
++        nlines = j * nlines;
++        if (nlines > HISTSIZE + RESIZEBUFFER + row) {
++            nlines = HISTSIZE + RESIZEBUFFER + row;
++            oy = -(nlines / j - oce - 1);
++        }
+     }
++    buf = xmalloc(nlines * sizeof(Line));
++    do {
++        if (!nx)
++            buf[++ny] = xmalloc(col * sizeof(Glyph));
++        if (!ox) {
++            line = TLINEABS(oy);
++            len = tlinelen(line);
++        }
++        if (oy == term.c.y) {
++            if (!ox)
++                len = MAX(len, term.c.x + 1);
++            /* update cursor */
++            if (cy < 0 && term.c.x - ox < col - nx) {
++                term.c.x = nx + term.c.x - ox, cy = ny;
++                UPDATEWRAPNEXT(0, col);
++            }
++        }
++        /* get reflowed lines in buf */
++        if (col - nx > len - ox) {
++            memcpy(&buf[ny][nx], &line[ox], (len-ox) * sizeof(Glyph));
++            nx += len - ox;
++            if (len == 0 || !(line[len - 1].mode & ATTR_WRAP)) {
++                for (j = nx; j < col; j++)
++                    tclearglyph(&buf[ny][j], 0);
++                nx = 0;
++            } else if (nx > 0) {
++                buf[ny][nx - 1].mode &= ~ATTR_WRAP;
++            }
++            ox = 0, oy++;
++        } else if (col - nx == len - ox) {
++            memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph));
++            ox = 0, oy++, nx = 0;
++        } else/* if (col - nx < len - ox) */ {
++            memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph));
++        ox += col - nx;
++            buf[ny][col - 1].mode |= ATTR_WRAP;
++            nx = 0;
++        }
++    } while (oy <= oce);
++    if (nx)
++        for (j = nx; j < col; j++)
++            tclearglyph(&buf[ny][j], 0);
+ 
+-    /*
+-     * slide screen to keep cursor where we expect it -
+-     * tscrollup would work here, but we can optimize to
+-     * memmove because we're freeing the earlier lines
+-     */
+-    for (i = 0; i <= term.c.y - row; i++) {
++    /* free extra lines */
++    for (i = row; i < term.row; i++)
+         free(term.line[i]);
+-        free(term.alt[i]);
++    /* resize to new height */
++    term.line = xrealloc(term.line, row * sizeof(Line));
++
++    bot = MIN(ny, row - 1);
++    scr = MAX(row - term.row, 0);
++    /* update y coordinate of cursor line end */
++    nce = MIN(oce + scr, bot);
++    /* update cursor y coordinate */
++    term.c.y = nce - (ny - cy);
++    if (term.c.y < 0) {
++        j = nce, nce = MIN(nce + -term.c.y, bot);
++        term.c.y += nce - j;
++        while (term.c.y < 0) {
++            free(buf[ny--]);
++            term.c.y++;
++        }
+     }
+-    /* ensure that both src and dst are not NULL */
+-    if (i > 0) {
+-        memmove(term.line, term.line + i, row * sizeof(Line));
+-        memmove(term.alt, term.alt + i, row * sizeof(Line));
++    /* allocate new rows */
++    for (i = row - 1; i > nce; i--) {
++        term.line[i] = xmalloc(col * sizeof(Glyph));
++        for (j = 0; j < col; j++)
++            tclearglyph(&term.line[i][j], 0);
+     }
+-    for (i += row; i < term.row; i++) {
++    /* fill visible area */
++    for (/*i = nce */; i >= term.row; i--, ny--)
++        term.line[i] = buf[ny];
++    for (/*i = term.row - 1 */; i >= 0; i--, ny--) {
+         free(term.line[i]);
+-        free(term.alt[i]);
++        term.line[i] = buf[ny];
++    }
++    /* fill lines in history buffer and update term.histf */
++    for (/*i = -1 */; ny >= 0 && i >= -HISTSIZE; i--, ny--) {
++        j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE;
++        free(term.hist[j]);
++        term.hist[j] = buf[ny];
+     }
++    term.histf = -i - 1;
++    term.scr = MIN(term.scr, term.histf);
++    /* resize rest of the history lines */
++    for (/*i = -term.histf - 1 */; i >= -HISTSIZE; i--) {
++        j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE;
++        term.hist[j] = xrealloc(term.hist[j], col * sizeof(Glyph));
++    }
++    free(buf);
++}
+ 
+-    /* resize to new height */
+-    term.line = xrealloc(term.line, row * sizeof(Line));
+-    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));
++void
++rscrolldown(int n)
++{
++    int i;
++    Line temp;
+ 
+-    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 = ' ';
+-        }
+-    }
++    /* can never be true as of now
++    if (IS_SET(MODE_ALTSCREEN))
++        return; */
+ 
+-    /* 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));
+-        term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
+-    }
++    if ((n = MIN(n, term.histf)) <= 0)
++        return;
+ 
+-    /* allocate any new rows */
+-    for (/* i = minrow */; i < row; i++) {
+-        term.line[i] = xmalloc(col * sizeof(Glyph));
+-        term.alt[i] = xmalloc(col * sizeof(Glyph));
++    for (i = term.c.y + n; i >= n; i--) {
++        temp = term.line[i];
++        term.line[i] = term.line[i-n];
++        term.line[i-n] = temp;
+     }
++    for (/*i = n - 1 */; i >= 0; i--) {
++        temp = term.line[i];
++        term.line[i] = term.hist[term.histi];
++        term.hist[term.histi] = temp;
++        term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
++    }
++    term.c.y += n;
++    term.histf -= n;
++    if ((i = term.scr - n) >= 0) {
++        term.scr = i;
++    } else {
++        term.scr = 0;
++        if (sel.ob.x != -1 && !sel.alt)
++            selmove(-i);
++    }
++}
++
++void
++tresize(int col, int row)
++{
++    int *bp;
++
++    /* col and row are always MAX(_, 1)
++    if (col < 1 || row < 1) {
++        fprintf(stderr, "tresize: error resizing to %dx%d\n", col, row);
++        return;
++    } */
++
++    term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
++    term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
+     if (col > term.col) {
+         bp = term.tabs + term.col;
+-
+         memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
+         while (--bp > term.tabs && !*bp)
+             /* nothing */ ;
+         for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
+             *bp = 1;
+     }
+-    /* update terminal size */
+-    term.col = col;
+-    term.row = row;
+-    /* reset scrolling region */
+-    tsetscroll(0, row-1);
+-    /* make use of the LIMIT in tmoveto */
+-    tmoveto(term.c.x, term.c.y);
+-    /* Clearing both screens (it makes dirty all lines) */
+-    c = term.c;
+-    for (i = 0; i < 2; i++) {
+-        if (mincol < col && 0 < minrow) {
+-            tclearregion(mincol, 0, col - 1, minrow - 1);
++
++    if (IS_SET(MODE_ALTSCREEN))
++        tresizealt(col, row);
++    else
++        tresizedef(col, row);
++}
++
++void
++tresizedef(int col, int row)
++{
++    int i, j;
++
++    /* return if dimensions haven't changed */
++    if (term.col == col && term.row == row) {
++        tfulldirt();
++        return;
++    }
++    if (col != term.col) {
++        if (!sel.alt)
++            selremove();
++        treflow(col, row);
++    } else {
++        /* slide screen up if otherwise cursor would get out of the screen */
++        if (term.c.y >= row) {
++            tscrollup(0, term.row - 1, term.c.y - row + 1, SCROLL_RESIZE);
++            term.c.y = row - 1;
+         }
+-        if (0 < col && minrow < row) {
+-            tclearregion(0, minrow, col - 1, row - 1);
++        for (i = row; i < term.row; i++)
++            free(term.line[i]);
++
++        /* resize to new height */
++        term.line = xrealloc(term.line, row * sizeof(Line));
++        /* allocate any new rows */
++        for (i = term.row; i < row; i++) {
++            term.line[i] = xmalloc(col * sizeof(Glyph));
++            for (j = 0; j < col; j++)
++                tclearglyph(&term.line[i][j], 0);
+         }
+-        tswapscreen();
+-        tcursor(CURSOR_LOAD);
++        /* scroll down as much as height has increased */
++        rscrolldown(row - term.row);
++    }
++    /* update terminal size */
++    term.col = col, term.row = row;
++    /* reset scrolling region */
++    term.top = 0, term.bot = row - 1;
++    /* dirty all lines */
++    tfulldirt();
++}
++
++void
++tresizealt(int col, int row)
++{
++    int i, j;
++
++    /* return if dimensions haven't changed */
++    if (term.col == col && term.row == row) {
++        tfulldirt();
++        return;
+     }
+-    term.c = c;
++    if (sel.alt)
++        selremove();
++    /* slide screen up if otherwise cursor would get out of the screen */
++    for (i = 0; i <= term.c.y - row; i++)
++        free(term.line[i]);
++    if (i > 0) {
++        /* ensure that both src and dst are not NULL */
++        memmove(term.line, term.line + i, row * sizeof(Line));
++        term.c.y = row - 1;
++    }
++    for (i += row; i < term.row; i++)
++        free(term.line[i]);
++    /* resize to new height */
++    term.line = xrealloc(term.line, row * sizeof(Line));
++    /* resize to new width */
++    for (i = 0; i < MIN(row, term.row); i++) {
++        term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
++        for (j = term.col; j < col; j++)
++            tclearglyph(&term.line[i][j], 0);
++    }
++    /* allocate any new rows */
++    for (/*i = MIN(row, term.row) */; i < row; i++) {
++        term.line[i] = xmalloc(col * sizeof(Glyph));
++        for (j = 0; j < col; j++)
++            tclearglyph(&term.line[i][j], 0);
++    }
++    /* update cursor */
++    if (term.c.x >= col) {
++        term.c.state &= ~CURSOR_WRAPNEXT;
++        term.c.x = col - 1;
++    } else {
++        UPDATEWRAPNEXT(1, col);
++    }
++    /* update terminal size */
++    term.col = col, term.row = row;
++    /* reset scrolling region */
++    term.top = 0, term.bot = row - 1;
++    /* dirty all lines */
++    tfulldirt();
+ }
+ 
+ void
+@@ -2709,9 +3057,8 @@ draw(void)
+         cx--;
+ 
+     drawregion(0, 0, term.col, term.row);
+-    if (term.scr == 0)
+-        xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
+-                term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
++    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();
+diff --git a/st.h b/st.h
+index 818a6f8..514ec08 100644
+--- a/st.h
++++ b/st.h
+@@ -22,17 +22,19 @@
+ 
+ enum glyph_attribute {
+     ATTR_NULL       = 0,
+-    ATTR_BOLD       = 1 << 0,
+-    ATTR_FAINT      = 1 << 1,
+-    ATTR_ITALIC     = 1 << 2,
+-    ATTR_UNDERLINE  = 1 << 3,
+-    ATTR_BLINK      = 1 << 4,
+-    ATTR_REVERSE    = 1 << 5,
+-    ATTR_INVISIBLE  = 1 << 6,
+-    ATTR_STRUCK     = 1 << 7,
+-    ATTR_WRAP       = 1 << 8,
+-    ATTR_WIDE       = 1 << 9,
+-    ATTR_WDUMMY     = 1 << 10,
++    ATTR_SET        = 1 << 0,
++    ATTR_BOLD       = 1 << 1,
++    ATTR_FAINT      = 1 << 2,
++    ATTR_ITALIC     = 1 << 3,
++    ATTR_UNDERLINE  = 1 << 4,
++    ATTR_BLINK      = 1 << 5,
++    ATTR_REVERSE    = 1 << 6,
++    ATTR_INVISIBLE  = 1 << 7,
++    ATTR_STRUCK     = 1 << 8,
++    ATTR_WRAP       = 1 << 9,
++    ATTR_WIDE       = 1 << 10,
++    ATTR_WDUMMY     = 1 << 11,
++    ATTR_SELECTED   = 1 << 12,
+     ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
+ };
+ 
+@@ -90,6 +92,7 @@ void toggleprinter(const Arg *);
+ 
+ int tattrset(int);
+ void tnew(int, int);
++int tisaltscreen(void);
+ void tresize(int, int);
+ void tsetdirtattr(int);
+ void ttyhangup(void);
diff --git a/patches/st-scrollback-ringbuffer-0.8.5.diff b/patches/st-scrollback-ringbuffer-0.8.5.diff
@@ -0,0 +1,730 @@
+commit 0663bdf11a409961da5b1120741a69814da8ce65
+Author: Timo Röhling <timo@gaussglocke.de>
+Date:   Tue Nov 23 19:45:33 2021 +0100
+
+    Terminal scrollback with ring buffer
+    
+    This patch adds a ring buffer for scrollback to the terminal.  The
+    advantage of using a ring buffer is that the common case, scrolling with
+    no static screen content, can be achieved very efficiently by
+    incrementing and decrementing the starting line (modulo buffer size).
+    
+    The scrollback buffer is limited to HISTSIZE lines in order to bound
+    memory usage. As the lines are allocated on demand, it is possible to
+    implement unlimited scrollback with few changes.  If the terminal is
+    reset, the scroll back buffer is reset, too.
+
+diff --git a/config.def.h b/config.def.h
+index 91ab8ca..e3b469b 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -201,6 +201,8 @@ static Shortcut shortcuts[] = {
+     { TERMMOD,              XK_Y,           selpaste,       {.i =  0} },
+     { ShiftMask,            XK_Insert,      selpaste,       {.i =  0} },
+     { TERMMOD,              XK_Num_Lock,    numlock,        {.i =  0} },
++    { ShiftMask,            XK_Page_Up,     kscrollup,      {.i = -1} },
++    { ShiftMask,            XK_Page_Down,   kscrolldown,    {.i = -1} },
+ };
+ 
+ /*
+diff --git a/st.c b/st.c
+index 51049ba..f9e24ba 100644
+--- a/st.c
++++ b/st.c
+@@ -43,6 +43,10 @@
+ #define ISCONTROL(c)        (ISCONTROLC0(c) || ISCONTROLC1(c))
+ #define ISDELIM(u)        (u && wcschr(worddelimiters, u))
+ 
++#define TSCREEN term.screen[IS_SET(MODE_ALTSCREEN)]
++#define TLINEOFFSET(y) (((y) + TSCREEN.cur - TSCREEN.off + TSCREEN.size) % TSCREEN.size)
++#define TLINE(y) (TSCREEN.buffer[TLINEOFFSET(y)])
++
+ enum term_mode {
+     MODE_WRAP        = 1 << 0,
+     MODE_INSERT      = 1 << 1,
+@@ -109,12 +113,21 @@ typedef struct {
+     int alt;
+ } Selection;
+ 
++/* Screen lines */
++typedef struct {
++    Line* buffer;  /* ring buffer */
++    int size;      /* size of buffer */
++    int cur;       /* start of active screen */
++    int off;       /* scrollback line offset */
++    TCursor sc;    /* saved cursor */
++} LineBuffer;
++
+ /* Internal representation of the screen */
+ typedef struct {
+     int row;      /* nb row */
+     int col;      /* nb col */
+-    Line *line;   /* screen */
+-    Line *alt;    /* alternate screen */
++    LineBuffer screen[2]; /* screen and alternate screen */
++    int linelen;  /* allocated line length */
+     int *dirty;   /* dirtyness of lines */
+     TCursor c;    /* cursor */
+     int ocx;      /* old cursor col */
+@@ -202,6 +215,8 @@ static void tdeftran(char);
+ static void tstrsequence(uchar);
+ 
+ static void drawregion(int, int, int, int);
++static void clearline(Line, Glyph, int, int);
++static Line ensureline(Line);
+ 
+ static void selnormalize(void);
+ static void selscroll(int, int);
+@@ -415,11 +430,12 @@ int
+ tlinelen(int y)
+ {
+     int i = term.col;
++    Line line = TLINE(y);
+ 
+-    if (term.line[y][i - 1].mode & ATTR_WRAP)
++    if (line[i - 1].mode & ATTR_WRAP)
+         return i;
+ 
+-    while (i > 0 && term.line[y][i - 1].u == ' ')
++    while (i > 0 && line[i - 1].u == ' ')
+         --i;
+ 
+     return i;
+@@ -528,7 +544,7 @@ selsnap(int *x, int *y, int direction)
+          * Snap around if the word wraps around at the end or
+          * beginning of a line.
+          */
+-        prevgp = &term.line[*y][*x];
++        prevgp = &TLINE(*y)[*x];
+         prevdelim = ISDELIM(prevgp->u);
+         for (;;) {
+             newx = *x + direction;
+@@ -543,14 +559,14 @@ selsnap(int *x, int *y, int direction)
+                     yt = *y, xt = *x;
+                 else
+                     yt = newy, xt = newx;
+-                if (!(term.line[yt][xt].mode & ATTR_WRAP))
++                if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
+                     break;
+             }
+ 
+             if (newx >= tlinelen(newy))
+                 break;
+ 
+-            gp = &term.line[newy][newx];
++            gp = &TLINE(newy)[newx];
+             delim = ISDELIM(gp->u);
+             if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
+                     || (delim && gp->u != prevgp->u)))
+@@ -571,14 +587,14 @@ selsnap(int *x, int *y, int direction)
+         *x = (direction < 0) ? 0 : term.col - 1;
+         if (direction < 0) {
+             for (; *y > 0; *y += direction) {
+-                if (!(term.line[*y-1][term.col-1].mode
++                if (!(TLINE(*y-1)[term.col-1].mode
+                         & ATTR_WRAP)) {
+                     break;
+                 }
+             }
+         } else if (direction > 0) {
+             for (; *y < term.row-1; *y += direction) {
+-                if (!(term.line[*y][term.col-1].mode
++                if (!(TLINE(*y)[term.col-1].mode
+                         & ATTR_WRAP)) {
+                     break;
+                 }
+@@ -609,13 +625,13 @@ getsel(void)
+         }
+ 
+         if (sel.type == SEL_RECTANGULAR) {
+-            gp = &term.line[y][sel.nb.x];
++            gp = &TLINE(y)[sel.nb.x];
+             lastx = sel.ne.x;
+         } else {
+-            gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
++            gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
+             lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
+         }
+-        last = &term.line[y][MIN(lastx, linelen-1)];
++        last = &TLINE(y)[MIN(lastx, linelen-1)];
+         while (last >= gp && last->u == ' ')
+             --last;
+ 
+@@ -956,12 +972,15 @@ int
+ tattrset(int attr)
+ {
+     int i, j;
++    int y = TLINEOFFSET(0);
+ 
+     for (i = 0; i < term.row-1; i++) {
++        Line line = TSCREEN.buffer[y];
+         for (j = 0; j < term.col-1; j++) {
+-            if (term.line[i][j].mode & attr)
++            if (line[j].mode & attr)
+                 return 1;
+         }
++        y = (y+1) % TSCREEN.size;
+     }
+ 
+     return 0;
+@@ -983,14 +1002,17 @@ void
+ tsetdirtattr(int attr)
+ {
+     int i, j;
++    int y = TLINEOFFSET(0);
+ 
+     for (i = 0; i < term.row-1; i++) {
++        Line line = TSCREEN.buffer[y];
+         for (j = 0; j < term.col-1; j++) {
+-            if (term.line[i][j].mode & attr) {
++            if (line[j].mode & attr) {
+                 tsetdirt(i, i);
+                 break;
+             }
+         }
++        y = (y+1) % TSCREEN.size;
+     }
+ }
+ 
+@@ -1003,27 +1025,19 @@ tfulldirt(void)
+ void
+ tcursor(int mode)
+ {
+-    static TCursor c[2];
+-    int alt = IS_SET(MODE_ALTSCREEN);
+-
+     if (mode == CURSOR_SAVE) {
+-        c[alt] = term.c;
++        TSCREEN.sc = term.c;
+     } else if (mode == CURSOR_LOAD) {
+-        term.c = c[alt];
+-        tmoveto(c[alt].x, c[alt].y);
++        term.c = TSCREEN.sc;
++        tmoveto(term.c.x, term.c.y);
+     }
+ }
+ 
+ void
+ treset(void)
+ {
+-    uint i;
+-
+-    term.c = (TCursor){{
+-        .mode = ATTR_NULL,
+-        .fg = defaultfg,
+-        .bg = defaultbg
+-    }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
++    int i, j;
++    Glyph g = (Glyph){ .fg = defaultfg, .bg = defaultbg};
+ 
+     memset(term.tabs, 0, term.col * sizeof(*term.tabs));
+     for (i = tabspaces; i < term.col; i += tabspaces)
+@@ -1035,17 +1049,37 @@ treset(void)
+     term.charset = 0;
+ 
+     for (i = 0; i < 2; i++) {
+-        tmoveto(0, 0);
+-        tcursor(CURSOR_SAVE);
+-        tclearregion(0, 0, term.col-1, term.row-1);
+-        tswapscreen();
++        term.screen[i].sc = (TCursor){{
++            .fg = defaultfg,
++            .bg = defaultbg
++        }};
++        term.screen[i].cur = 0;
++        term.screen[i].off = 0;
++        for (j = 0; j < term.row; ++j) {
++            if (term.col != term.linelen)
++                term.screen[i].buffer[j] = xrealloc(term.screen[i].buffer[j], term.col * sizeof(Glyph));
++            clearline(term.screen[i].buffer[j], g, 0, term.col);
++        }
++        for (j = term.row; j < term.screen[i].size; ++j) {
++            free(term.screen[i].buffer[j]);
++            term.screen[i].buffer[j] = NULL;
++        }
+     }
++    tcursor(CURSOR_LOAD);
++    term.linelen = term.col;
++    tfulldirt();
+ }
+ 
+ void
+ tnew(int col, int row)
+ {
+-    term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
++    int i;
++    term = (Term){};
++    term.screen[0].buffer = xmalloc(HISTSIZE * sizeof(Line));
++    term.screen[0].size = HISTSIZE;
++    term.screen[1].buffer = NULL;
++    for (i = 0; i < HISTSIZE; ++i) term.screen[0].buffer[i] = NULL;
++
+     tresize(col, row);
+     treset();
+ }
+@@ -1053,14 +1087,42 @@ tnew(int col, int row)
+ void
+ tswapscreen(void)
+ {
+-    Line *tmp = term.line;
+-
+-    term.line = term.alt;
+-    term.alt = tmp;
+     term.mode ^= MODE_ALTSCREEN;
+     tfulldirt();
+ }
+ 
++void
++kscrollup(const Arg *a)
++{
++    int n = a->i;
++
++    if (IS_SET(MODE_ALTSCREEN))
++        return;
++
++    if (n < 0) n = (-n) * term.row;
++    if (n > TSCREEN.size - term.row - TSCREEN.off) n = TSCREEN.size - term.row - TSCREEN.off;
++    while (!TLINE(-n)) --n;
++    TSCREEN.off += n;
++    selscroll(0, n);
++    tfulldirt();
++}
++
++void
++kscrolldown(const Arg *a)
++{
++
++    int n = a->i;
++
++    if (IS_SET(MODE_ALTSCREEN))
++        return;
++
++    if (n < 0) n = (-n) * term.row;
++    if (n > TSCREEN.off) n = TSCREEN.off;
++    TSCREEN.off -= n;
++    selscroll(0, -n);
++    tfulldirt();
++}
++
+ void
+ tscrolldown(int orig, int n)
+ {
+@@ -1069,15 +1131,29 @@ tscrolldown(int orig, int n)
+ 
+     LIMIT(n, 0, term.bot-orig+1);
+ 
+-    tsetdirt(orig, term.bot-n);
+-    tclearregion(0, term.bot-n+1, term.col-1, term.bot);
++    /* Ensure that lines are allocated */
++    for (i = -n; i < 0; i++) {
++        TLINE(i) = ensureline(TLINE(i));
++    }
+ 
+-    for (i = term.bot; i >= orig+n; i--) {
+-        temp = term.line[i];
+-        term.line[i] = term.line[i-n];
+-        term.line[i-n] = temp;
++    /* Shift non-scrolling areas in ring buffer */
++    for (i = term.bot+1; i < term.row; i++) {
++        temp = TLINE(i);
++        TLINE(i) = TLINE(i-n);
++        TLINE(i-n) = temp;
++    }
++    for (i = 0; i < orig; i++) {
++        temp = TLINE(i);
++        TLINE(i) = TLINE(i-n);
++        TLINE(i-n) = temp;
+     }
+ 
++    /* Scroll buffer */
++    TSCREEN.cur = (TSCREEN.cur + TSCREEN.size - n) % TSCREEN.size;
++    /* Clear lines that have entered the view */
++    tclearregion(0, orig, term.linelen-1, orig+n-1);
++    /* Redraw portion of the screen that has scrolled */
++    tsetdirt(orig+n-1, term.bot);
+     selscroll(orig, n);
+ }
+ 
+@@ -1089,15 +1165,29 @@ tscrollup(int orig, int n)
+ 
+     LIMIT(n, 0, term.bot-orig+1);
+ 
+-    tclearregion(0, orig, term.col-1, orig+n-1);
+-    tsetdirt(orig+n, term.bot);
++    /* Ensure that lines are allocated */
++    for (i = term.row; i < term.row + n; i++) {
++        TLINE(i) = ensureline(TLINE(i));
++    }
+ 
+-    for (i = orig; i <= term.bot-n; i++) {
+-        temp = term.line[i];
+-        term.line[i] = term.line[i+n];
+-        term.line[i+n] = temp;
++    /* Shift non-scrolling areas in ring buffer */
++    for (i = orig-1; i >= 0; i--) {
++        temp = TLINE(i);
++        TLINE(i) = TLINE(i+n);
++        TLINE(i+n) = temp;
++    }
++    for (i = term.row-1; i >term.bot; i--) {
++        temp = TLINE(i);
++        TLINE(i) = TLINE(i+n);
++        TLINE(i+n) = temp;
+     }
+ 
++    /* Scroll buffer */
++    TSCREEN.cur = (TSCREEN.cur + n) % TSCREEN.size;
++    /* Clear lines that have entered the view */
++    tclearregion(0, term.bot-n+1, term.linelen-1, term.bot);
++    /* Redraw portion of the screen that has scrolled */
++    tsetdirt(orig, term.bot-n+1);
+     selscroll(orig, -n);
+ }
+ 
+@@ -1201,6 +1291,7 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
+         "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
+         "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
+     };
++    Line line = TLINE(y);
+ 
+     /*
+      * The table is proudly stolen from rxvt.
+@@ -1209,25 +1300,25 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
+        BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
+         utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
+ 
+-    if (term.line[y][x].mode & ATTR_WIDE) {
++    if (line[x].mode & ATTR_WIDE) {
+         if (x+1 < term.col) {
+-            term.line[y][x+1].u = ' ';
+-            term.line[y][x+1].mode &= ~ATTR_WDUMMY;
++            line[x+1].u = ' ';
++            line[x+1].mode &= ~ATTR_WDUMMY;
+         }
+-    } else if (term.line[y][x].mode & ATTR_WDUMMY) {
+-        term.line[y][x-1].u = ' ';
+-        term.line[y][x-1].mode &= ~ATTR_WIDE;
++    } else if (line[x].mode & ATTR_WDUMMY) {
++        line[x-1].u = ' ';
++        line[x-1].mode &= ~ATTR_WIDE;
+     }
+ 
+     term.dirty[y] = 1;
+-    term.line[y][x] = *attr;
+-    term.line[y][x].u = u;
++    line[x] = *attr;
++    line[x].u = u;
+ }
+ 
+ void
+ tclearregion(int x1, int y1, int x2, int y2)
+ {
+-    int x, y, temp;
++    int x, y, L, S, temp;
+     Glyph *gp;
+ 
+     if (x1 > x2)
+@@ -1235,15 +1326,16 @@ tclearregion(int x1, int y1, int x2, int y2)
+     if (y1 > y2)
+         temp = y1, y1 = y2, y2 = temp;
+ 
+-    LIMIT(x1, 0, term.col-1);
+-    LIMIT(x2, 0, term.col-1);
++    LIMIT(x1, 0, term.linelen-1);
++    LIMIT(x2, 0, term.linelen-1);
+     LIMIT(y1, 0, term.row-1);
+     LIMIT(y2, 0, term.row-1);
+ 
++    L = TLINEOFFSET(y1);
+     for (y = y1; y <= y2; y++) {
+         term.dirty[y] = 1;
+         for (x = x1; x <= x2; x++) {
+-            gp = &term.line[y][x];
++            gp = &TSCREEN.buffer[L][x];
+             if (selected(x, y))
+                 selclear();
+             gp->fg = term.c.attr.fg;
+@@ -1251,6 +1343,7 @@ tclearregion(int x1, int y1, int x2, int y2)
+             gp->mode = 0;
+             gp->u = ' ';
+         }
++        L = (L + 1) % TSCREEN.size;
+     }
+ }
+ 
+@@ -1265,7 +1358,7 @@ tdeletechar(int n)
+     dst = term.c.x;
+     src = term.c.x + n;
+     size = term.col - src;
+-    line = term.line[term.c.y];
++    line = TLINE(term.c.y);
+ 
+     memmove(&line[dst], &line[src], size * sizeof(Glyph));
+     tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
+@@ -1282,7 +1375,7 @@ tinsertblank(int n)
+     dst = term.c.x + n;
+     src = term.c.x;
+     size = term.col - dst;
+-    line = term.line[term.c.y];
++    line = TLINE(term.c.y);
+ 
+     memmove(&line[dst], &line[src], size * sizeof(Glyph));
+     tclearregion(src, term.c.y, dst - 1, term.c.y);
+@@ -2103,7 +2196,7 @@ tdumpline(int n)
+     char buf[UTF_SIZ];
+     const Glyph *bp, *end;
+ 
+-    bp = &term.line[n][0];
++    bp = &TLINE(n)[0];
+     end = &bp[MIN(tlinelen(n), term.col) - 1];
+     if (bp != end || bp->u != ' ') {
+         for ( ; bp <= end; ++bp)
+@@ -2486,11 +2579,11 @@ check_control_code:
+     if (selected(term.c.x, term.c.y))
+         selclear();
+ 
+-    gp = &term.line[term.c.y][term.c.x];
++    gp = &TLINE(term.c.y)[term.c.x];
+     if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
+         gp->mode |= ATTR_WRAP;
+         tnewline(1);
+-        gp = &term.line[term.c.y][term.c.x];
++        gp = &TLINE(term.c.y)[term.c.x];
+     }
+ 
+     if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
+@@ -2498,7 +2591,7 @@ check_control_code:
+ 
+     if (term.c.x+width > term.col) {
+         tnewline(1);
+-        gp = &term.line[term.c.y][term.c.x];
++        gp = &TLINE(term.c.y)[term.c.x];
+     }
+ 
+     tsetchar(u, &term.c.attr, term.c.x, term.c.y);
+@@ -2529,6 +2622,11 @@ twrite(const char *buf, int buflen, int show_ctrl)
+     Rune u;
+     int n;
+ 
++    if (TSCREEN.off) {
++        TSCREEN.off = 0;
++        tfulldirt();
++    }
++
+     for (n = 0; n < buflen; n += charsize) {
+         if (IS_SET(MODE_UTF8)) {
+             /* process a complete utf8 char */
+@@ -2555,56 +2653,85 @@ twrite(const char *buf, int buflen, int show_ctrl)
+ }
+ 
+ void
+-tresize(int col, int row)
++clearline(Line line, Glyph g, int x, int xend)
+ {
+     int i;
++    g.mode = 0;
++    g.u = ' ';
++    for (i = x; i < xend; ++i) {
++        line[i] = g;
++    }
++}
++
++Line
++ensureline(Line line)
++{
++    if (!line) {
++        line = xmalloc(term.linelen * sizeof(Glyph));
++    }
++    return line;
++}
++
++void
++tresize(int col, int row)
++{
++    int i, j;
+     int minrow = MIN(row, term.row);
+     int mincol = MIN(col, term.col);
++    int linelen = MAX(col, term.linelen);
+     int *bp;
+-    TCursor c;
+ 
+-    if (col < 1 || row < 1) {
++    if (col < 1 || row < 1 || row > HISTSIZE) {
+         fprintf(stderr,
+                 "tresize: error resizing to %dx%d\n", col, row);
+         return;
+     }
+ 
+-    /*
+-     * slide screen to keep cursor where we expect it -
+-     * tscrollup would work here, but we can optimize to
+-     * memmove because we're freeing the earlier lines
+-     */
+-    for (i = 0; i <= term.c.y - row; i++) {
+-        free(term.line[i]);
+-        free(term.alt[i]);
++    /* Shift buffer to keep the cursor where we expect it */
++    if (row <= term.c.y) {
++        term.screen[0].cur = (term.screen[0].cur - row + term.c.y + 1) % term.screen[0].size;
++    }
++
++    /* Resize and clear line buffers as needed */
++    if (linelen > term.linelen) {
++        for (i = 0; i < term.screen[0].size; ++i) {
++            if (term.screen[0].buffer[i]) {
++                term.screen[0].buffer[i] = xrealloc(term.screen[0].buffer[i], linelen * sizeof(Glyph));
++                clearline(term.screen[0].buffer[i], term.c.attr, term.linelen, linelen);
++            }
++        }
++        for (i = 0; i < minrow; ++i) {
++            term.screen[1].buffer[i] = xrealloc(term.screen[1].buffer[i], linelen * sizeof(Glyph));
++            clearline(term.screen[1].buffer[i], term.c.attr, term.linelen, linelen);
++        }
+     }
+-    /* ensure that both src and dst are not NULL */
+-    if (i > 0) {
+-        memmove(term.line, term.line + i, row * sizeof(Line));
+-        memmove(term.alt, term.alt + i, row * sizeof(Line));
++    /* Allocate all visible lines for regular line buffer */
++    for (j = term.screen[0].cur, i = 0; i < row; ++i, j = (j + 1) % term.screen[0].size)
++    {
++        if (!term.screen[0].buffer[j]) {
++            term.screen[0].buffer[j] = xmalloc(linelen * sizeof(Glyph));
++        }
++        if (i >= term.row) {
++            clearline(term.screen[0].buffer[j], term.c.attr, 0, linelen);
++        }
+     }
+-    for (i += row; i < term.row; i++) {
+-        free(term.line[i]);
+-        free(term.alt[i]);
++    /* Resize alt screen */
++    term.screen[1].cur = 0;
++    term.screen[1].size = row;
++    for (i = row; i < term.row; ++i) {
++        free(term.screen[1].buffer[i]);
++    }
++    term.screen[1].buffer = xrealloc(term.screen[1].buffer, row * sizeof(Line));
++    for (i = term.row; i < row; ++i) {
++        term.screen[1].buffer[i] = xmalloc(linelen * sizeof(Glyph));
++        clearline(term.screen[1].buffer[i], term.c.attr, 0, linelen);
+     }
+ 
+     /* resize to new height */
+-    term.line = xrealloc(term.line, row * sizeof(Line));
+-    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));
+ 
+-    /* 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));
+-        term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
+-    }
+-
+-    /* allocate any new rows */
+-    for (/* i = minrow */; i < row; i++) {
+-        term.line[i] = xmalloc(col * sizeof(Glyph));
+-        term.alt[i] = xmalloc(col * sizeof(Glyph));
+-    }
++    /* fix tabstops */
+     if (col > term.col) {
+         bp = term.tabs + term.col;
+ 
+@@ -2614,26 +2741,16 @@ tresize(int col, int row)
+         for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
+             *bp = 1;
+     }
++
+     /* update terminal size */
+     term.col = col;
+     term.row = row;
++    term.linelen = linelen;
+     /* reset scrolling region */
+     tsetscroll(0, row-1);
+     /* make use of the LIMIT in tmoveto */
+     tmoveto(term.c.x, term.c.y);
+-    /* Clearing both screens (it makes dirty all lines) */
+-    c = term.c;
+-    for (i = 0; i < 2; i++) {
+-        if (mincol < col && 0 < minrow) {
+-            tclearregion(mincol, 0, col - 1, minrow - 1);
+-        }
+-        if (0 < col && minrow < row) {
+-            tclearregion(0, minrow, col - 1, row - 1);
+-        }
+-        tswapscreen();
+-        tcursor(CURSOR_LOAD);
+-    }
+-    term.c = c;
++    tfulldirt();
+ }
+ 
+ void
+@@ -2645,14 +2762,15 @@ resettitle(void)
+ void
+ drawregion(int x1, int y1, int x2, int y2)
+ {
+-    int y;
++    int y, L;
+ 
++    L = TLINEOFFSET(y1);
+     for (y = y1; y < y2; y++) {
+-        if (!term.dirty[y])
+-            continue;
+-
+-        term.dirty[y] = 0;
+-        xdrawline(term.line[y], x1, y, x2);
++        if (term.dirty[y]) {
++            term.dirty[y] = 0;
++            xdrawline(TSCREEN.buffer[L], x1, y, x2);
++        }
++        L = (L + 1) % TSCREEN.size;
+     }
+ }
+ 
+@@ -2667,14 +2785,15 @@ draw(void)
+     /* adjust cursor position */
+     LIMIT(term.ocx, 0, term.col-1);
+     LIMIT(term.ocy, 0, term.row-1);
+-    if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
++    if (TLINE(term.ocy)[term.ocx].mode & ATTR_WDUMMY)
+         term.ocx--;
+-    if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
++    if (TLINE(term.c.y)[cx].mode & ATTR_WDUMMY)
+         cx--;
+ 
+     drawregion(0, 0, term.col, term.row);
+-    xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
+-            term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
++    if (TSCREEN.off == 0)
++        xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx],
++                term.ocx, term.ocy, TLINE(term.ocy)[term.ocx]);
+     term.ocx = cx;
+     term.ocy = term.c.y;
+     xfinishdraw();
+diff --git a/st.h b/st.h
+index 519b9bd..b48e810 100644
+--- a/st.h
++++ b/st.h
+@@ -19,6 +19,7 @@
+ 
+ #define TRUECOLOR(r,g,b)    (1 << 24 | (r) << 16 | (g) << 8 | (b))
+ #define IS_TRUECOL(x)        (1 << 24 & (x))
++#define HISTSIZE            2000
+ 
+ enum glyph_attribute {
+     ATTR_NULL       = 0,
+diff --git a/x.c b/x.c
+index 8a16faa..1bb5853 100644
+--- a/x.c
++++ b/x.c
+@@ -59,6 +59,8 @@ static void zoom(const Arg *);
+ static void zoomabs(const Arg *);
+ static void zoomreset(const Arg *);
+ static void ttysend(const Arg *);
++void kscrollup(const Arg *);
++void kscrolldown(const Arg *);
+ 
+ /* config.h for applying patches and the configuration. */
+ #include "config.h"
diff --git a/patches/st-universcroll-0.8.4.diff b/patches/st-universcroll-0.8.4.diff
@@ -0,0 +1,90 @@
+From 9726b1e58352126252412e101432e64d46fc51ca Mon Sep 17 00:00:00 2001
+From: Dennis Lee <dennis@dennislee.xyz>
+Date: Sun, 28 Jun 2020 23:01:03 -0700
+Subject: [PATCH] universcroll: mouse wheel only scroll in all modes
+
+Scroll normally via scroll(1), without Shift, when outside of
+`MODE_ALTSCREEN`. Inside an alt screen, continue to scroll normally
+without Shift; in this mode, your scrolling is automatically translated
+into ^Y and ^E. It just werks!
+
+Based on the existing mouse-altscreen patch
+https://st.suckless.org/patches/scrollback/
+adapted for st(1) 0.8.4 and scroll(1).
+---
+ config.def.h | 10 +++++-----
+ st.c         |  5 +++++
+ st.h         |  1 +
+ x.c          |  2 ++
+ 4 files changed, 13 insertions(+), 5 deletions(-)
+
+diff --git a/config.def.h b/config.def.h
+index 6f05dce..62e87da 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -173,11 +173,11 @@ static uint forcemousemod = ShiftMask;
+  * Beware that overloading Button1 will disable the selection.
+  */
+ static MouseShortcut mshortcuts[] = {
+-    /* mask                 button   function        argument       release */
+-    { XK_ANY_MOD,           Button2, selpaste,       {.i = 0},      1 },
+-    { ShiftMask,            Button4, ttysend,        {.s = "\033[5;2~"} },
++    /* mask                 button   function        argument      release alt */
++    { XK_ANY_MOD,           Button2, selpaste,       {.i = 0},           1 },
++    { XK_ANY_MOD,           Button4, ttysend,        {.s = "\033[5;2~"}, 0, -1 },
+     { XK_ANY_MOD,           Button4, ttysend,        {.s = "\031"} },
+-    { ShiftMask,            Button5, ttysend,        {.s = "\033[6;2~"} },
++    { XK_ANY_MOD,           Button5, ttysend,        {.s = "\033[6;2~"}, 0, -1 },
+     { XK_ANY_MOD,           Button5, ttysend,        {.s = "\005"} },
+ };
+
+diff --git a/st.c b/st.c
+index 76b7e0d..1f65453 100644
+--- a/st.c
++++ b/st.c
+@@ -1047,6 +1047,11 @@ tnew(int col, int row)
+     treset();
+ }
+
++int tisaltscr(void)
++{
++    return IS_SET(MODE_ALTSCREEN);
++}
++
+ void
+ tswapscreen(void)
+ {
+diff --git a/st.h b/st.h
+index 3d351b6..39cc054 100644
+--- a/st.h
++++ b/st.h
+@@ -87,6 +87,7 @@ void sendbreak(const Arg *);
+ void toggleprinter(const Arg *);
+
+ int tattrset(int);
++int tisaltscr(void);
+ void tnew(int, int);
+ void tresize(int, int);
+ void tsetdirtattr(int);
+diff --git a/x.c b/x.c
+index 210f184..210dde9 100644
+--- a/x.c
++++ b/x.c
+@@ -34,6 +34,7 @@ typedef struct {
+     void (*func)(const Arg *);
+     const Arg arg;
+     uint  release;
++    int  altscrn;  /* 0: don't care, -1: not alt screen, 1: alt screen */
+ } MouseShortcut;
+
+ typedef struct {
+@@ -446,6 +447,7 @@ mouseaction(XEvent *e, uint release)
+     for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
+         if (ms->release == release &&
+             ms->button == e->xbutton.button &&
++            (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) &&
+             (match(ms->mod, state) ||  /* exact or forced */
+              match(ms->mod, state & ~forcemousemod))) {
+             ms->func(&(ms->arg));
+--
+2.27.0
diff --git a/st.c b/st.c
@@ -35,6 +35,7 @@
 #define ESC_ARG_SIZ   16
 #define STR_BUF_SIZ   ESC_BUF_SIZ
 #define STR_ARG_SIZ   ESC_ARG_SIZ
+#define HISTSIZE      2000
 
 /* macros */
 #define IS_SET(flag)        ((term.mode & (flag)) != 0)
@@ -42,6 +43,9 @@
 #define ISCONTROLC1(c)        (BETWEEN(c, 0x80, 0x9f))
 #define ISCONTROL(c)        (ISCONTROLC0(c) || ISCONTROLC1(c))
 #define ISDELIM(u)        (u && wcschr(worddelimiters, u))
+#define TLINE(y)        ((y) < term.scr ? term.hist[((y) + term.histi - \
+                term.scr + HISTSIZE + 1) % HISTSIZE] : \
+                term.line[(y) - term.scr])
 
 enum term_mode {
     MODE_WRAP        = 1 << 0,
@@ -115,6 +119,9 @@ typedef struct {
     int col;      /* nb col */
     Line *line;   /* screen */
     Line *alt;    /* alternate screen */
+    Line hist[HISTSIZE]; /* history buffer */
+    int histi;    /* history index */
+    int scr;      /* scroll back */
     int *dirty;   /* dirtyness of lines */
     TCursor c;    /* cursor */
     int ocx;      /* old cursor col */
@@ -185,8 +192,8 @@ static void tnewline(int);
 static void tputtab(int);
 static void tputc(Rune);
 static void treset(void);
-static void tscrollup(int, int);
-static void tscrolldown(int, int);
+static void tscrollup(int, int, int);
+static void tscrolldown(int, int, int);
 static void tsetattr(const int *, int);
 static void tsetchar(Rune, const Glyph *, int, int);
 static void tsetdirt(int, int);
@@ -409,10 +416,10 @@ tlinelen(int y)
 {
     int i = term.col;
 
-    if (term.line[y][i - 1].mode & ATTR_WRAP)
+    if (TLINE(y)[i - 1].mode & ATTR_WRAP)
         return i;
 
-    while (i > 0 && term.line[y][i - 1].u == ' ')
+    while (i > 0 && TLINE(y)[i - 1].u == ' ')
         --i;
 
     return i;
@@ -521,7 +528,7 @@ selsnap(int *x, int *y, int direction)
          * Snap around if the word wraps around at the end or
          * beginning of a line.
          */
-        prevgp = &term.line[*y][*x];
+        prevgp = &TLINE(*y)[*x];
         prevdelim = ISDELIM(prevgp->u);
         for (;;) {
             newx = *x + direction;
@@ -536,14 +543,14 @@ selsnap(int *x, int *y, int direction)
                     yt = *y, xt = *x;
                 else
                     yt = newy, xt = newx;
-                if (!(term.line[yt][xt].mode & ATTR_WRAP))
+                if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
                     break;
             }
 
             if (newx >= tlinelen(newy))
                 break;
 
-            gp = &term.line[newy][newx];
+            gp = &TLINE(newy)[newx];
             delim = ISDELIM(gp->u);
             if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
                     || (delim && gp->u != prevgp->u)))
@@ -564,14 +571,14 @@ selsnap(int *x, int *y, int direction)
         *x = (direction < 0) ? 0 : term.col - 1;
         if (direction < 0) {
             for (; *y > 0; *y += direction) {
-                if (!(term.line[*y-1][term.col-1].mode
+                if (!(TLINE(*y-1)[term.col-1].mode
                         & ATTR_WRAP)) {
                     break;
                 }
             }
         } else if (direction > 0) {
             for (; *y < term.row-1; *y += direction) {
-                if (!(term.line[*y][term.col-1].mode
+                if (!(TLINE(*y)[term.col-1].mode
                         & ATTR_WRAP)) {
                     break;
                 }
@@ -602,13 +609,13 @@ getsel(void)
         }
 
         if (sel.type == SEL_RECTANGULAR) {
-            gp = &term.line[y][sel.nb.x];
+            gp = &TLINE(y)[sel.nb.x];
             lastx = sel.ne.x;
         } else {
-            gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
+            gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
             lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
         }
-        last = &term.line[y][MIN(lastx, linelen-1)];
+        last = &TLINE(y)[MIN(lastx, linelen-1)];
         while (last >= gp && last->u == ' ')
             --last;
 
@@ -844,6 +851,9 @@ void
 ttywrite(const char *s, size_t n, int may_echo)
 {
     const char *next;
+    Arg arg = (Arg) { .i = term.scr };
+
+    kscrolldown(&arg);
 
     if (may_echo && IS_SET(MODE_ECHO))
         twrite(s, n, 1);
@@ -1043,6 +1053,11 @@ tnew(int col, int row)
     treset();
 }
 
+int tisaltscr(void)
+{
+    return IS_SET(MODE_ALTSCREEN);
+}
+
 void
 tswapscreen(void)
 {
@@ -1055,13 +1070,53 @@ tswapscreen(void)
 }
 
 void
-tscrolldown(int orig, int n)
+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();
+    }
+}
+
+void
+tscrolldown(int orig, int n, int copyhist)
 {
     int i;
     Line temp;
 
     LIMIT(n, 0, term.bot-orig+1);
 
+    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;
+    }
+
     tsetdirt(orig, term.bot-n);
     tclearregion(0, term.bot-n+1, term.col-1, term.bot);
 
@@ -1071,17 +1126,28 @@ tscrolldown(int orig, int n)
         term.line[i-n] = temp;
     }
 
-    selscroll(orig, n);
+    if (term.scr == 0)
+        selscroll(orig, n);
 }
 
 void
-tscrollup(int orig, int n)
+tscrollup(int orig, int n, int copyhist)
 {
     int i;
     Line temp;
 
     LIMIT(n, 0, term.bot-orig+1);
 
+    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);
+
     tclearregion(0, orig, term.col-1, orig+n-1);
     tsetdirt(orig+n, term.bot);
 
@@ -1091,7 +1157,8 @@ tscrollup(int orig, int n)
         term.line[i+n] = temp;
     }
 
-    selscroll(orig, -n);
+    if (term.scr == 0)
+        selscroll(orig, -n);
 }
 
 void
@@ -1120,7 +1187,7 @@ tnewline(int first_col)
     int y = term.c.y;
 
     if (y == term.bot) {
-        tscrollup(term.top, 1);
+        tscrollup(term.top, 1, 1);
     } else {
         y++;
     }
@@ -1285,14 +1352,14 @@ void
 tinsertblankline(int n)
 {
     if (BETWEEN(term.c.y, term.top, term.bot))
-        tscrolldown(term.c.y, n);
+        tscrolldown(term.c.y, n, 0);
 }
 
 void
 tdeleteline(int n)
 {
     if (BETWEEN(term.c.y, term.top, term.bot))
-        tscrollup(term.c.y, n);
+        tscrollup(term.c.y, n, 0);
 }
 
 int32_t
@@ -1729,11 +1796,11 @@ csihandle(void)
         break;
     case 'S': /* SU -- Scroll <n> line up */
         DEFAULT(csiescseq.arg[0], 1);
-        tscrollup(term.top, csiescseq.arg[0]);
+        tscrollup(term.top, csiescseq.arg[0], 0);
         break;
     case 'T': /* SD -- Scroll <n> line down */
         DEFAULT(csiescseq.arg[0], 1);
-        tscrolldown(term.top, csiescseq.arg[0]);
+        tscrolldown(term.top, csiescseq.arg[0], 0);
         break;
     case 'L': /* IL -- Insert <n> blank lines */
         DEFAULT(csiescseq.arg[0], 1);
@@ -2163,6 +2230,28 @@ tstrsequence(uchar c)
 }
 
 void
+tupdatebgcolor(int oldbg, int newbg)
+{
+    for (int y = 0; y < term.row; y++) {
+        for (int x = 0; x < term.col; x++) {
+            if (term.line[y][x].bg == oldbg)
+                term.line[y][x].bg = newbg;
+        }
+    }
+}
+
+void
+tupdatefgcolor(int oldfg, int newfg)
+{
+    for (int y = 0; y < term.row; y++) {
+        for (int x = 0; x < term.col; x++) {
+            if (term.line[y][x].fg == oldfg)
+                term.line[y][x].fg = newfg;
+        }
+    }
+}
+
+void
 tcontrolcode(uchar ascii)
 {
     switch (ascii) {
@@ -2296,7 +2385,7 @@ eschandle(uchar ascii)
         return 0;
     case 'D': /* IND -- Linefeed */
         if (term.c.y == term.bot) {
-            tscrollup(term.top, 1);
+            tscrollup(term.top, 1, 1);
         } else {
             tmoveto(term.c.x, term.c.y+1);
         }
@@ -2309,7 +2398,7 @@ eschandle(uchar ascii)
         break;
     case 'M': /* RI -- Reverse index */
         if (term.c.y == term.top) {
-            tscrolldown(term.top, 1);
+            tscrolldown(term.top, 1, 1);
         } else {
             tmoveto(term.c.x, term.c.y-1);
         }
@@ -2523,7 +2612,7 @@ twrite(const char *buf, int buflen, int show_ctrl)
 void
 tresize(int col, int row)
 {
-    int i;
+    int i, j;
     int minrow = MIN(row, term.row);
     int mincol = MIN(col, term.col);
     int *bp;
@@ -2560,6 +2649,14 @@ tresize(int col, int row)
     term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
     term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
 
+    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 = ' ';
+        }
+    }
+
     /* 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));
@@ -2618,7 +2715,7 @@ drawregion(int x1, int y1, int x2, int y2)
             continue;
 
         term.dirty[y] = 0;
-        xdrawline(term.line[y], x1, y, x2);
+        xdrawline(TLINE(y), x1, y, x2);
     }
 }
 
@@ -2639,8 +2736,9 @@ draw(void)
         cx--;
 
     drawregion(0, 0, term.col, term.row);
-    xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
-            term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
+    if (term.scr == 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;
     xfinishdraw();
diff --git a/st.h b/st.h
@@ -81,15 +81,20 @@ void die(const char *, ...);
 void redraw(void);
 void draw(void);
 
+void kscrolldown(const Arg *);
+void kscrollup(const Arg *);
 void printscreen(const Arg *);
 void printsel(const Arg *);
 void sendbreak(const Arg *);
 void toggleprinter(const Arg *);
 
 int tattrset(int);
+int tisaltscr(void);
 void tnew(int, int);
 void tresize(int, int);
 void tsetdirtattr(int);
+void tupdatebgcolor(int, int);
+void tupdatefgcolor(int, int);
 void ttyhangup(void);
 int ttynew(const char *, char *, const char *, char **);
 size_t ttyread(void);
diff --git a/tags b/tags
@@ -0,0 +1,577 @@
+$(OBJ)    Makefile    /^$(OBJ): config.h config.mk$/;"    t
+-1000,7 +1066,8    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -1000,7 +1066,8 @@ tsetdirtattr(int attr)$/;"    h    modifiedFile:a/st.c
+-1003,27 +1025,19    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -1003,27 +1025,19 @@ tfulldirt(void)$/;"    h    modifiedFile:a/st.c
+-1017,51 +1084,116    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -1017,51 +1084,116 @@ tcursor(int mode)$/;"    h    modifiedFile:a/st.c
+-1035,17 +1049,37    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -1035,17 +1049,37 @@ treset(void)$/;"    h    modifiedFile:a/st.c
+-1047,6 +1047,11    patches/st-universcroll-0.8.4.diff    /^@@ -1047,6 +1047,11 @@ tnew(int col, int row)$/;"    h    modifiedFile:a/st.c
+-105,6 +105,7    patches/st-alpha-20220206-0.8.5.diff    /^@@ -105,6 +105,7 @@ typedef struct {$/;"    h    modifiedFile:a/x.c
+-1053,14 +1087,42    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -1053,14 +1087,42 @@ tnew(int col, int row)$/;"    h    modifiedFile:a/st.c
+-1061,13 +1071,53    patches/st-scrollback-20210507-4536f46.diff    /^@@ -1061,13 +1071,53 @@ tswapscreen(void)$/;"    h    modifiedFile:a/st.c
+-1069,15 +1131,29    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -1069,15 +1131,29 @@ tscrolldown(int orig, int n)$/;"    h    modifiedFile:a/st.c
+-1069,17 +1201,22    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -1069,17 +1201,22 @@ kscrolldown(const Arg* a)$/;"    h    modifiedFile:a/st.c
+-1077,17 +1127,28    patches/st-scrollback-20210507-4536f46.diff    /^@@ -1077,17 +1127,28 @@ tscrolldown(int orig, int n)$/;"    h    modifiedFile:a/st.c
+-1087,92 +1224,118    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -1087,92 +1224,118 @@ kscrollup(const Arg* a)$/;"    h    modifiedFile:a/st.c
+-1089,15 +1165,29    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -1089,15 +1165,29 @@ tscrollup(int orig, int n)$/;"    h    modifiedFile:a/st.c
+-109,12 +113,21    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -109,12 +113,21 @@ typedef struct {$/;"    h    modifiedFile:a/st.c
+-1097,7 +1158,8    patches/st-scrollback-20210507-4536f46.diff    /^@@ -1097,7 +1158,8 @@ tscrollup(int orig, int n)$/;"    h    modifiedFile:a/st.c
+-1118,11 +1127,23    patches/st-alpha-20220206-0.8.5.diff    /^@@ -1118,11 +1127,23 @@ xinit(int cols, int rows)$/;"    h    modifiedFile:a/x.c
+-1126,7 +1188,7    patches/st-scrollback-20210507-4536f46.diff    /^@@ -1126,7 +1188,7 @@ tnewline(int first_col)$/;"    h    modifiedFile:a/st.c
+-1132,7 +1153,7    patches/st-alpha-20220206-0.8.5.diff    /^@@ -1132,7 +1153,7 @@ xinit(int cols, int rows)$/;"    h    modifiedFile:a/x.c
+-115,6 +119,9    patches/st-scrollback-20210507-4536f46.diff    /^@@ -115,6 +119,9 @@ typedef struct {$/;"    h    modifiedFile:a/st.c
+-1152,19 +1173,15    patches/st-alpha-20220206-0.8.5.diff    /^@@ -1152,19 +1173,15 @@ xinit(int cols, int rows)$/;"    h    modifiedFile:a/x.c
+-1152,8 +1156,8    patches/st-anysize-20220718-baa9357.diff    /^@@ -1152,8 +1156,8 @@ xinit(int cols, int rows)$/;"    h    modifiedFile:a/x.c
+-118,10 +138,11    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -118,10 +138,11 @@ typedef struct {$/;"    h    modifiedFile:a/st.c
+-1182,7 +1345,7    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -1182,7 +1345,7 @@ tnewline(int first_col)$/;"    h    modifiedFile:a/st.c
+-1201,6 +1291,7    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -1201,6 +1291,7 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)$/;"    h    modifiedFile:a/st.c
+-1209,25 +1300,25    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -1209,25 +1300,25 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)$/;"    h    modifiedFile:a/st.c
+-1235,15 +1326,16    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -1235,15 +1326,16 @@ tclearregion(int x1, int y1, int x2, int y2)$/;"    h    modifiedFile:a/st.c
+-1242,7 +1246,7    patches/st-anysize-20220718-baa9357.diff    /^@@ -1242,7 +1246,7 @@ xinit(int cols, int rows)$/;"    h    modifiedFile:a/x.c
+-1251,6 +1343,7    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -1251,6 +1343,7 @@ tclearregion(int x1, int y1, int x2, int y2)$/;"    h    modifiedFile:a/st.c
+-126,3 +126,4    patches/st-alpha-20220206-0.8.5.diff    /^@@ -126,3 +126,4 @@ extern unsigned int tabspaces;$/;"    h    modifiedFile:a/st.h
+-1265,7 +1358,7    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -1265,7 +1358,7 @@ tdeletechar(int n)$/;"    h    modifiedFile:a/st.c
+-1272,89 +1435,93    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -1272,89 +1435,93 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)$/;"    h    modifiedFile:a/st.c
+-1282,7 +1375,7    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -1282,7 +1375,7 @@ tinsertblank(int n)$/;"    h    modifiedFile:a/st.c
+-129,9 +121,9    patches/st-gruvbox-dark-0.8.5.diff    /^@@ -129,9 +121,9 @@ static const char *colorname[] = {$/;"    h    modifiedFile:a/config.def.h
+-1291,14 +1353,14    patches/st-scrollback-20210507-4536f46.diff    /^@@ -1291,14 +1353,14 @@ void$/;"    h    modifiedFile:a/st.c
+-133,13 +133,20    patches/st-blinking_cursor-20211116-2f6e597.diff    /^@@ -133,13 +133,20 @@ static unsigned int defaultcs = 256;$/;"    h    modifiedFile:a/config.def.h
+-1375,7 +1379,7    patches/st-anysize-20220718-baa9357.diff    /^@@ -1375,7 +1379,7 @@ void$/;"    h    modifiedFile:a/x.c
+-1465,17 +1469,17    patches/st-anysize-20220718-baa9357.diff    /^@@ -1465,17 +1469,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, /;"    h    modifiedFile:a/x.c
+-1528,7 +1695,7    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -1528,7 +1695,7 @@ tsetscroll(int t, int b)$/;"    h    modifiedFile:a/st.c
+-1529,29 +1530,44    patches/st-blinking_cursor-20211116-2f6e597.diff    /^@@ -1529,29 +1530,44 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)$/;"    h    modifiedFile:a/x.c
+-1569,35 +1573,35    patches/st-anysize-20220718-baa9357.diff    /^@@ -1569,35 +1573,35 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)$/;"    h    modifiedFile:a/x.c
+-1589,25 +1756,18    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -1589,25 +1756,18 @@ tsetmode(int priv, int set, const int *args, int narg)$/;"    h    modifiedFile:a/st.c
+-16,7 +16,7    patches/st-alpha-20220206-0.8.5.diff    /^@@ -16,7 +16,7 @@ PKG_CONFIG = pkg-config$/;"    h    modifiedFile:a/config.mk
+-1659,7 +1819,7    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -1659,7 +1819,7 @@ void$/;"    h    modifiedFile:a/st.c
+-1708,9 +1724,12    patches/st-blinking_cursor-20211116-2f6e597.diff    /^@@ -1708,9 +1724,12 @@ xsetmode(int set, unsigned int flags)$/;"    h    modifiedFile:a/x.c
+-173,11 +173,11    patches/st-universcroll-0.8.4.diff    /^@@ -173,11 +173,11 @@ static uint forcemousemod = ShiftMask;$/;"    h    modifiedFile:a/config.def.h
+-1735,11 +1797,11    patches/st-scrollback-20210507-4536f46.diff    /^@@ -1735,11 +1797,11 @@ csihandle(void)$/;"    h    modifiedFile:a/st.c
+-1757,20 +1917,30    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -1757,20 +1917,30 @@ csihandle(void)$/;"    h    modifiedFile:a/st.c
+-176,6 +176,8    patches/st-scrollback-mouse-20220127-2c5edf2.diff    /^@@ -176,6 +176,8 @@ static uint forcemousemod = ShiftMask;$/;"    h    modifiedFile:a/config.def.h
+-1778,24 +1948,24    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -1778,24 +1948,24 @@ csihandle(void)$/;"    h    modifiedFile:a/st.c
+-179,26 +200,37    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -179,26 +200,37 @@ static void tprinter(char *, size_t);$/;"    h    modifiedFile:a/st.c
+-1809,9 +1979,11    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -1809,9 +1979,11 @@ csihandle(void)$/;"    h    modifiedFile:a/st.c
+-1833,9 +2005,9    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -1833,9 +2005,9 @@ csihandle(void)$/;"    h    modifiedFile:a/st.c
+-184,8 +191,8    patches/st-scrollback-20210507-4536f46.diff    /^@@ -184,8 +191,8 @@ static void tnewline(int);$/;"    h    modifiedFile:a/st.c
+-185,6 +187,7    patches/st-colorschemes-0.8.5.diff    /^@@ -185,6 +187,7 @@ static void mousesel(XEvent *, int);$/;"    h    modifiedFile:a/x.c
+-19,6 +19,7    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -19,6 +19,7 @@$/;"    h    modifiedFile:a/st.h
+-1954,6 +1973,10    patches/st-blinking_cursor-20211116-2f6e597.diff    /^@@ -1954,6 +1973,10 @@ run(void)$/;"    h    modifiedFile:a/x.c
+-1964,7 +1987,7    patches/st-blinking_cursor-20211116-2f6e597.diff    /^@@ -1964,7 +1987,7 @@ run(void)$/;"    h    modifiedFile:a/x.c
+-199,6 +199,8    patches/st-scrollback-20210507-4536f46.diff    /^@@ -199,6 +199,8 @@ static Shortcut shortcuts[] = {$/;"    h    modifiedFile:a/config.def.h
+-2000,7 +2023,7    patches/st-blinking_cursor-20211116-2f6e597.diff    /^@@ -2000,7 +2023,7 @@ main(int argc, char *argv[])$/;"    h    modifiedFile:a/x.c
+-2008,6 +2011,47    patches/st-colorschemes-0.8.5.diff    /^@@ -2008,6 +2011,47 @@ usage(void)$/;"    h    modifiedFile:a/x.c
+-201,6 +201,8    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -201,6 +201,8 @@ static Shortcut shortcuts[] = {$/;"    h    modifiedFile:a/config.def.h
+-201,6 +242,17    patches/st-colorschemes-0.8.5.diff    /^@@ -201,6 +242,17 @@ static Shortcut shortcuts[] = {$/;"    h    modifiedFile:a/config.def.h
+-2019,6 +2036,9    patches/st-alpha-20220206-0.8.5.diff    /^@@ -2019,6 +2036,9 @@ main(int argc, char *argv[])$/;"    h    modifiedFile:a/x.c
+-202,6 +215,8    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -202,6 +215,8 @@ static void tdeftran(char);$/;"    h    modifiedFile:a/st.c
+-2060,6 +2104,12    patches/st-colorschemes-0.8.5.diff    /^@@ -2060,6 +2104,12 @@ main(int argc, char *argv[])$/;"    h    modifiedFile:a/x.c
+-2103,7 +2196,7    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -2103,7 +2196,7 @@ tdumpline(int n)$/;"    h    modifiedFile:a/st.c
+-212,7 +244,10    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -212,7 +244,10 @@ static void tstrsequence(uchar);$/;"    h    modifiedFile:a/st.c
+-2128,16 +2300,8    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -2128,16 +2300,8 @@ tdumpsel(void)$/;"    h    modifiedFile:a/st.c
+-2196,6 +2196,28    patches/st-colorschemes-0.8.5.diff    /^@@ -2196,6 +2196,28 @@ tstrsequence(uchar c)$/;"    h    modifiedFile:a/st.c
+-22,17 +22,19    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -22,17 +22,19 @@$/;"    h    modifiedFile:a/st.h
+-2251,7 +2313,7    patches/st-scrollback-20210507-4536f46.diff    /^@@ -2251,7 +2313,7 @@ eschandle(uchar ascii)$/;"    h    modifiedFile:a/st.c
+-2264,7 +2326,7    patches/st-scrollback-20210507-4536f46.diff    /^@@ -2264,7 +2326,7 @@ eschandle(uchar ascii)$/;"    h    modifiedFile:a/st.c
+-2358,7 +2522,7    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -2358,7 +2522,7 @@ eschandle(uchar ascii)$/;"    h    modifiedFile:a/st.c
+-2371,7 +2535,7    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -2371,7 +2535,7 @@ eschandle(uchar ascii)$/;"    h    modifiedFile:a/st.c
+-243,6 +244,7    patches/st-alpha-20220206-0.8.5.diff    /^@@ -243,6 +244,7 @@ static char *usedfont = NULL;$/;"    h    modifiedFile:a/x.c
+-2474,7 +2536,7    patches/st-scrollback-20210507-4536f46.diff    /^@@ -2474,7 +2536,7 @@ twrite(const char *buf, int buflen, int show_ctrl)$/;"    h    modifiedFile:a/st.c
+-2486,11 +2579,11    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -2486,11 +2579,11 @@ check_control_code:$/;"    h    modifiedFile:a/st.c
+-2498,7 +2591,7    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -2498,7 +2591,7 @@ check_control_code:$/;"    h    modifiedFile:a/st.c
+-2511,6 +2573,14    patches/st-scrollback-20210507-4536f46.diff    /^@@ -2511,6 +2573,14 @@ tresize(int col, int row)$/;"    h    modifiedFile:a/st.c
+-2511,7 +2675,8    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -2511,7 +2675,8 @@ check_control_code:$/;"    h    modifiedFile:a/st.c
+-2529,6 +2622,11    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -2529,6 +2622,11 @@ twrite(const char *buf, int buflen, int show_ctrl)$/;"    h    modifiedFile:a/st.c
+-253,6 +253,7    patches/st-blinking_cursor-20211116-2f6e597.diff    /^@@ -253,6 +253,7 @@ static char *opt_name  = NULL;$/;"    h    modifiedFile:a/x.c
+-2546,6 +2711,7    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -2546,6 +2711,7 @@ check_control_code:$/;"    h    modifiedFile:a/st.c
+-2555,56 +2653,85    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -2555,56 +2653,85 @@ twrite(const char *buf, int buflen, int show_ctrl)$/;"    h    modifiedFile:a/st.c
+-2569,7 +2639,7    patches/st-scrollback-20210507-4536f46.diff    /^@@ -2569,7 +2639,7 @@ drawregion(int x1, int y1, int x2, int y2)$/;"    h    modifiedFile:a/st.c
+-2583,93 +2749,275    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -2583,93 +2749,275 @@ twrite(const char *buf, int buflen, int show_ctrl)$/;"    h    modifiedFile:a/st.c
+-2590,8 +2660,9    patches/st-scrollback-20210507-4536f46.diff    /^@@ -2590,8 +2660,9 @@ draw(void)$/;"    h    modifiedFile:a/st.c
+-2614,26 +2741,16    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -2614,26 +2741,16 @@ tresize(int col, int row)$/;"    h    modifiedFile:a/st.c
+-2645,14 +2762,15    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -2645,14 +2762,15 @@ resettitle(void)$/;"    h    modifiedFile:a/st.c
+-2667,14 +2785,15    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -2667,14 +2785,15 @@ draw(void)$/;"    h    modifiedFile:a/st.c
+-2709,9 +3057,8    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -2709,9 +3057,8 @@ draw(void)$/;"    h    modifiedFile:a/st.c
+-331,7 +332,7    patches/st-anysize-20220718-baa9357.diff    /^@@ -331,7 +332,7 @@ ttysend(const Arg *arg)$/;"    h    modifiedFile:a/x.c
+-339,7 +340,7    patches/st-anysize-20220718-baa9357.diff    /^@@ -339,7 +340,7 @@ evcol(XEvent *e)$/;"    h    modifiedFile:a/x.c
+-34,6 +34,7    patches/st-universcroll-0.8.4.diff    /^@@ -34,6 +34,7 @@ typedef struct {$/;"    h    modifiedFile:a/x.c
+-35,6 +35,7    patches/st-scrollback-20210507-4536f46.diff    /^@@ -35,6 +35,7 @@$/;"    h    modifiedFile:a/st.c
+-36,6 +36,7    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -36,6 +36,7 @@$/;"    h    modifiedFile:a/st.c
+-412,17 +447,46    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -412,17 +447,46 @@ selinit(void)$/;"    h    modifiedFile:a/st.c
+-415,11 +430,12    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -415,11 +430,12 @@ int$/;"    h    modifiedFile:a/st.c
+-416,10 +423,10    patches/st-scrollback-20210507-4536f46.diff    /^@@ -416,10 +423,10 @@ tlinelen(int y)$/;"    h    modifiedFile:a/st.c
+-42,6 +43,9    patches/st-scrollback-20210507-4536f46.diff    /^@@ -42,6 +43,9 @@$/;"    h    modifiedFile:a/st.c
+-43,6 +43,10    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -43,6 +43,10 @@$/;"    h    modifiedFile:a/st.c
+-43,9 +44,22    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -43,9 +44,22 @@$/;"    h    modifiedFile:a/st.c
+-446,6 +447,7    patches/st-universcroll-0.8.4.diff    /^@@ -446,6 +447,7 @@ mouseaction(XEvent *e, uint release)$/;"    h    modifiedFile:a/x.c
+-462,10 +526,11    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -462,10 +526,11 @@ selextend(int col, int row, int type, int done)$/;"    h    modifiedFile:a/st.c
+-492,36 +557,43    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -492,36 +557,43 @@ selnormalize(void)$/;"    h    modifiedFile:a/st.c
+-528,7 +535,7    patches/st-scrollback-20210507-4536f46.diff    /^@@ -528,7 +535,7 @@ selsnap(int *x, int *y, int direction)$/;"    h    modifiedFile:a/st.c
+-528,7 +544,7    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -528,7 +544,7 @@ selsnap(int *x, int *y, int direction)$/;"    h    modifiedFile:a/st.c
+-536,7 +608,7    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -536,7 +608,7 @@ selsnap(int *x, int *y, int direction)$/;"    h    modifiedFile:a/st.c
+-543,14 +550,14    patches/st-scrollback-20210507-4536f46.diff    /^@@ -543,14 +550,14 @@ selsnap(int *x, int *y, int direction)$/;"    h    modifiedFile:a/st.c
+-543,14 +559,14    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -543,14 +559,14 @@ selsnap(int *x, int *y, int direction)$/;"    h    modifiedFile:a/st.c
+-547,13 +619,13    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -547,13 +619,13 @@ selsnap(int *x, int *y, int direction)$/;"    h    modifiedFile:a/st.c
+-57,6 +71,12    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -57,6 +71,12 @@ enum term_mode {$/;"    h    modifiedFile:a/st.c
+-570,18 +642,14    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -570,18 +642,14 @@ selsnap(int *x, int *y, int direction)$/;"    h    modifiedFile:a/st.c
+-571,14 +578,14    patches/st-scrollback-20210507-4536f46.diff    /^@@ -571,14 +578,14 @@ selsnap(int *x, int *y, int direction)$/;"    h    modifiedFile:a/st.c
+-571,14 +587,14    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -571,14 +587,14 @@ selsnap(int *x, int *y, int direction)$/;"    h    modifiedFile:a/st.c
+-59,6 +59,8    patches/st-colorschemes-0.8.5.diff    /^@@ -59,6 +59,8 @@ static void zoom(const Arg *);$/;"    h    modifiedFile:a/x.c
+-59,6 +59,8    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -59,6 +59,8 @@ static void zoom(const Arg *);$/;"    h    modifiedFile:a/x.c
+-592,40 +660,34    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -592,40 +660,34 @@ char *$/;"    h    modifiedFile:a/st.c
+-609,13 +616,13    patches/st-scrollback-20210507-4536f46.diff    /^@@ -609,13 +616,13 @@ getsel(void)$/;"    h    modifiedFile:a/st.c
+-609,13 +625,13    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -609,13 +625,13 @@ getsel(void)$/;"    h    modifiedFile:a/st.c
+-636,10 +698,10    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -636,10 +698,10 @@ getsel(void)$/;"    h    modifiedFile:a/st.c
+-648,9 +710,15    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -648,9 +710,15 @@ selclear(void)$/;"    h    modifiedFile:a/st.c
+-736,7 +738,7    patches/st-alpha-20220206-0.8.5.diff    /^@@ -736,7 +738,7 @@ xresize(int col, int row)$/;"    h    modifiedFile:a/x.c
+-739,6 +740,9    patches/st-anysize-20220718-baa9357.diff    /^@@ -739,6 +740,9 @@ cresize(int width, int height)$/;"    h    modifiedFile:a/x.c
+-785,7 +788,7    patches/st-colorschemes-0.8.5.diff    /^@@ -785,7 +788,7 @@ xloadcols(void)$/;"    h    modifiedFile:a/x.c
+-796,6 +798,13    patches/st-alpha-20220206-0.8.5.diff    /^@@ -796,6 +798,13 @@ xloadcols(void)$/;"    h    modifiedFile:a/x.c
+-81,6 +81,7    patches/st-anysize-20220718-baa9357.diff    /^@@ -81,6 +81,7 @@ typedef XftGlyphFontSpec GlyphFontSpec;$/;"    h    modifiedFile:a/x.c
+-81,6 +81,8    patches/st-scrollback-20210507-4536f46.diff    /^@@ -81,6 +81,8 @@ void die(const char *, ...);$/;"    h    modifiedFile:a/st.h
+-850,6 +857,9    patches/st-scrollback-20210507-4536f46.diff    /^@@ -850,6 +857,9 @@ void$/;"    h    modifiedFile:a/st.c
+-851,10 +919,8    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -851,10 +919,8 @@ void$/;"    h    modifiedFile:a/st.c
+-869,8 +873,8    patches/st-anysize-20220718-baa9357.diff    /^@@ -869,8 +873,8 @@ xhints(void)$/;"    h    modifiedFile:a/x.c
+-87,6 +87,7    patches/st-universcroll-0.8.4.diff    /^@@ -87,6 +87,7 @@ void sendbreak(const Arg *);$/;"    h    modifiedFile:a/st.h
+-90,6 +90,8    patches/st-colorschemes-0.8.5.diff    /^@@ -90,6 +90,8 @@ int tattrset(int);$/;"    h    modifiedFile:a/st.h
+-90,6 +92,7    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -90,6 +92,7 @@ void toggleprinter(const Arg *);$/;"    h    modifiedFile:a/st.h
+-93,46 +93,87    patches/st-colorschemes-0.8.5.diff    /^@@ -93,46 +93,87 @@ char *termname = "st-256color";$/;"    h    modifiedFile:a/config.def.h
+-93,6 +93,9    patches/st-alpha-20220206-0.8.5.diff    /^@@ -93,6 +93,9 @@ char *termname = "st-256color";$/;"    h    modifiedFile:a/config.def.h
+-956,12 +972,15    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -956,12 +972,15 @@ int$/;"    h    modifiedFile:a/st.c
+-96,32 +96,24    patches/st-gruvbox-dark-0.8.5.diff    /^@@ -96,32 +96,24 @@ unsigned int tabspaces = 8;$/;"    h    modifiedFile:a/config.def.h
+-983,14 +1002,17    patches/st-scrollback-ringbuffer-0.8.5.diff    /^@@ -983,14 +1002,17 @@ void$/;"    h    modifiedFile:a/st.c
+-990,7 +1056,7    patches/st-scrollback-reflow-0.8.5.diff    /^@@ -990,7 +1056,7 @@ tsetdirtattr(int attr)$/;"    h    modifiedFile:a/st.c
+.c.o    Makefile    /^.c.o:$/;"    t
+ARGBEGIN    arg.h    /^#define ARGBEGIN    /;"    d
+ARGC    arg.h    /^#define ARGC(/;"    d
+ARGEND    arg.h    /^#define ARGEND    /;"    d
+ARGF    arg.h    /^#define ARGF(/;"    d
+ARG_H__    arg.h    /^#define ARG_H__$/;"    d
+ATTRCMP    st.h    /^#define ATTRCMP(/;"    d
+ATTR_BLINK    st.h    /^    ATTR_BLINK      = 1 << 4,$/;"    e    enum:glyph_attribute
+ATTR_BOLD    st.h    /^    ATTR_BOLD       = 1 << 0,$/;"    e    enum:glyph_attribute
+ATTR_BOLD_FAINT    st.h    /^    ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,$/;"    e    enum:glyph_attribute
+ATTR_FAINT    st.h    /^    ATTR_FAINT      = 1 << 1,$/;"    e    enum:glyph_attribute
+ATTR_INVISIBLE    st.h    /^    ATTR_INVISIBLE  = 1 << 6,$/;"    e    enum:glyph_attribute
+ATTR_ITALIC    st.h    /^    ATTR_ITALIC     = 1 << 2,$/;"    e    enum:glyph_attribute
+ATTR_NULL    st.h    /^    ATTR_NULL       = 0,$/;"    e    enum:glyph_attribute
+ATTR_REVERSE    st.h    /^    ATTR_REVERSE    = 1 << 5,$/;"    e    enum:glyph_attribute
+ATTR_STRUCK    st.h    /^    ATTR_STRUCK     = 1 << 7,$/;"    e    enum:glyph_attribute
+ATTR_UNDERLINE    st.h    /^    ATTR_UNDERLINE  = 1 << 3,$/;"    e    enum:glyph_attribute
+ATTR_WDUMMY    st.h    /^    ATTR_WDUMMY     = 1 << 10,$/;"    e    enum:glyph_attribute
+ATTR_WIDE    st.h    /^    ATTR_WIDE       = 1 << 9,$/;"    e    enum:glyph_attribute
+ATTR_WRAP    st.h    /^    ATTR_WRAP       = 1 << 8,$/;"    e    enum:glyph_attribute
+AUTHORS    st.1    /^.SH AUTHORS$/;"    s    title:ST
+Arg    st.h    /^} Arg;$/;"    t    typeref:union:__anon7c9e12e2020a
+BETWEEN    st.h    /^#define BETWEEN(/;"    d
+BUGS    st.1    /^.SH BUGS$/;"    s    title:ST
+CUSTOMIZATION    st.1    /^.SH CUSTOMIZATION$/;"    s    title:ST
+ColorScheme    config.def.h    /^} ColorScheme;$/;"    t    typeref:struct:__anon9258968e0108
+ColorScheme    config.h    /^} ColorScheme;$/;"    t    typeref:struct:__anon41b17d910108
+DEFAULT    st.h    /^#define DEFAULT(/;"    d
+DESCRIPTION    st.1    /^.SH DESCRIPTION$/;"    s    title:ST
+DIVCEIL    st.h    /^#define DIVCEIL(/;"    d
+EARGF    arg.h    /^#define EARGF(/;"    d
+Glyph    st.h    /^#define Glyph /;"    d
+Glyph    st.h    /^} Glyph;$/;"    t    typeref:struct:__anon7c9e12e20108
+INCS    config.mk    /^INCS = -I$(X11INC) \\$/;"    m
+IS_TRUECOL    st.h    /^#define IS_TRUECOL(/;"    d
+LEN    st.h    /^#define LEN(/;"    d
+LIBS    config.mk    /^LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\\$/;"    m
+LICENSE    st.1    /^.SH LICENSE$/;"    s    title:ST
+LIMIT    st.h    /^#define LIMIT(/;"    d
+Line    st.h    /^typedef Glyph *Line;$/;"    t    typeref:typename:Glyph *
+MANPREFIX    config.mk    /^MANPREFIX = $(PREFIX)\/share\/man$/;"    m
+MAX    st.h    /^#define MAX(/;"    d
+MIN    st.h    /^#define MIN(/;"    d
+MODBIT    st.h    /^#define MODBIT(/;"    d
+MODE_8BIT    win.h    /^    MODE_8BIT        = 1 << 10,$/;"    e    enum:win_mode
+MODE_APPCURSOR    win.h    /^    MODE_APPCURSOR   = 1 << 8,$/;"    e    enum:win_mode
+MODE_APPKEYPAD    win.h    /^    MODE_APPKEYPAD   = 1 << 2,$/;"    e    enum:win_mode
+MODE_BLINK    win.h    /^    MODE_BLINK       = 1 << 11,$/;"    e    enum:win_mode
+MODE_BRCKTPASTE    win.h    /^    MODE_BRCKTPASTE  = 1 << 16,$/;"    e    enum:win_mode
+MODE_FBLINK    win.h    /^    MODE_FBLINK      = 1 << 12,$/;"    e    enum:win_mode
+MODE_FOCUS    win.h    /^    MODE_FOCUS       = 1 << 13,$/;"    e    enum:win_mode
+MODE_FOCUSED    win.h    /^    MODE_FOCUSED     = 1 << 1,$/;"    e    enum:win_mode
+MODE_HIDE    win.h    /^    MODE_HIDE        = 1 << 7,$/;"    e    enum:win_mode
+MODE_KBDLOCK    win.h    /^    MODE_KBDLOCK     = 1 << 6,$/;"    e    enum:win_mode
+MODE_MOUSE    win.h    /^    MODE_MOUSE       = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\\$/;"    e    enum:win_mode
+MODE_MOUSEBTN    win.h    /^    MODE_MOUSEBTN    = 1 << 3,$/;"    e    enum:win_mode
+MODE_MOUSEMANY    win.h    /^    MODE_MOUSEMANY   = 1 << 15,$/;"    e    enum:win_mode
+MODE_MOUSEMOTION    win.h    /^    MODE_MOUSEMOTION = 1 << 4,$/;"    e    enum:win_mode
+MODE_MOUSESGR    win.h    /^    MODE_MOUSESGR    = 1 << 9,$/;"    e    enum:win_mode
+MODE_MOUSEX10    win.h    /^    MODE_MOUSEX10    = 1 << 14,$/;"    e    enum:win_mode
+MODE_NUMLOCK    win.h    /^    MODE_NUMLOCK     = 1 << 17,$/;"    e    enum:win_mode
+MODE_REVERSE    win.h    /^    MODE_REVERSE     = 1 << 5,$/;"    e    enum:win_mode
+MODE_VISIBLE    win.h    /^    MODE_VISIBLE     = 1 << 0,$/;"    e    enum:win_mode
+MODKEY    config.def.h    /^#define MODKEY /;"    d
+MODKEY    config.h    /^#define MODKEY /;"    d
+Makefile    Makefile    1;"    F    epoch:1667356258
+NAME    st.1    /^.SH NAME$/;"    s    title:ST
+OBJ    Makefile    /^OBJ = $(SRC:.c=.o)$/;"    m
+OPTIONS    st.1    /^.SH OPTIONS$/;"    s    title:ST
+PKG_CONFIG    config.mk    /^PKG_CONFIG = pkg-config$/;"    m
+PREFIX    config.mk    /^PREFIX = \/usr\/local$/;"    m
+Rune    st.h    /^typedef uint_least32_t Rune;$/;"    t    typeref:typename:uint_least32_t
+SEE ALSO    st.1    /^.SH SEE ALSO$/;"    s    title:ST
+SEL_EMPTY    st.h    /^    SEL_EMPTY = 1,$/;"    e    enum:selection_mode
+SEL_IDLE    st.h    /^    SEL_IDLE = 0,$/;"    e    enum:selection_mode
+SEL_READY    st.h    /^    SEL_READY = 2$/;"    e    enum:selection_mode
+SEL_RECTANGULAR    st.h    /^    SEL_RECTANGULAR = 2$/;"    e    enum:selection_type
+SEL_REGULAR    st.h    /^    SEL_REGULAR = 1,$/;"    e    enum:selection_type
+SHORTCUTS    st.1    /^.SH SHORTCUTS$/;"    s    title:ST
+SNAP_LINE    st.h    /^    SNAP_LINE = 2$/;"    e    enum:selection_snap
+SNAP_WORD    st.h    /^    SNAP_WORD = 1,$/;"    e    enum:selection_snap
+SRC    Makefile    /^SRC = st.c x.c$/;"    m
+ST    st.1    /^.TH ST 1 st\\-VERSION$/;"    t
+STCFLAGS    config.mk    /^STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS)$/;"    m
+STCPPFLAGS    config.mk    /^STCPPFLAGS = -DVERSION=\\"$(VERSION)\\" -D_XOPEN_SOURCE=600$/;"    m
+STLDFLAGS    config.mk    /^STLDFLAGS = $(LIBS) $(LDFLAGS)$/;"    m
+SYNOPSIS    st.1    /^.SH SYNOPSIS$/;"    s    title:ST
+TERMMOD    config.def.h    /^#define TERMMOD /;"    d
+TERMMOD    config.h    /^#define TERMMOD /;"    d
+TIMEDIFF    st.h    /^#define TIMEDIFF(/;"    d
+TRUECOLOR    st.h    /^#define TRUECOLOR(/;"    d
+VERSION    config.mk    /^VERSION = 0.8.5$/;"    m
+X11INC    config.mk    /^X11INC = \/usr\/X11R6\/include$/;"    m
+X11LIB    config.mk    /^X11LIB = \/usr\/X11R6\/lib$/;"    m
+a/config.def.h    patches/st-alpha-20220206-0.8.5.diff    /^--- a\/config.def.h$/;"    m
+a/config.def.h    patches/st-blinking_cursor-20211116-2f6e597.diff    /^--- a\/config.def.h$/;"    m
+a/config.def.h    patches/st-colorschemes-0.8.5.diff    /^--- a\/config.def.h$/;"    m
+a/config.def.h    patches/st-gruvbox-dark-0.8.5.diff    /^--- a\/config.def.h$/;"    m
+a/config.def.h    patches/st-scrollback-20210507-4536f46.diff    /^--- a\/config.def.h$/;"    m
+a/config.def.h    patches/st-scrollback-mouse-20220127-2c5edf2.diff    /^--- a\/config.def.h$/;"    m
+a/config.def.h    patches/st-scrollback-ringbuffer-0.8.5.diff    /^--- a\/config.def.h$/;"    m
+a/config.def.h    patches/st-universcroll-0.8.4.diff    /^--- a\/config.def.h$/;"    m
+a/config.mk    patches/st-alpha-20220206-0.8.5.diff    /^--- a\/config.mk$/;"    m
+a/st.c    patches/st-colorschemes-0.8.5.diff    /^--- a\/st.c$/;"    m
+a/st.c    patches/st-scrollback-20210507-4536f46.diff    /^--- a\/st.c$/;"    m
+a/st.c    patches/st-scrollback-reflow-0.8.5.diff    /^--- a\/st.c$/;"    m
+a/st.c    patches/st-scrollback-ringbuffer-0.8.5.diff    /^--- a\/st.c$/;"    m
+a/st.c    patches/st-universcroll-0.8.4.diff    /^--- a\/st.c$/;"    m
+a/st.h    patches/st-alpha-20220206-0.8.5.diff    /^--- a\/st.h$/;"    m
+a/st.h    patches/st-colorschemes-0.8.5.diff    /^--- a\/st.h$/;"    m
+a/st.h    patches/st-scrollback-20210507-4536f46.diff    /^--- a\/st.h$/;"    m
+a/st.h    patches/st-scrollback-reflow-0.8.5.diff    /^--- a\/st.h$/;"    m
+a/st.h    patches/st-scrollback-ringbuffer-0.8.5.diff    /^--- a\/st.h$/;"    m
+a/st.h    patches/st-universcroll-0.8.4.diff    /^--- a\/st.h$/;"    m
+a/x.c    patches/st-alpha-20220206-0.8.5.diff    /^--- a\/x.c$/;"    m
+a/x.c    patches/st-anysize-20220718-baa9357.diff    /^--- a\/x.c$/;"    m
+a/x.c    patches/st-blinking_cursor-20211116-2f6e597.diff    /^--- a\/x.c$/;"    m
+a/x.c    patches/st-colorschemes-0.8.5.diff    /^--- a\/x.c$/;"    m
+a/x.c    patches/st-scrollback-ringbuffer-0.8.5.diff    /^--- a\/x.c$/;"    m
+a/x.c    patches/st-universcroll-0.8.4.diff    /^--- a\/x.c$/;"    m
+all    Makefile    /^all: options st$/;"    t
+allowaltscreen    config.def.h    /^int allowaltscreen = 1;$/;"    v    typeref:typename:int
+allowaltscreen    config.h    /^int allowaltscreen = 1;$/;"    v    typeref:typename:int
+allowwindowops    config.def.h    /^int allowwindowops = 0;$/;"    v    typeref:typename:int
+allowwindowops    config.h    /^int allowwindowops = 0;$/;"    v    typeref:typename:int
+alpha    config.def.h    /^float alpha = 0.92;$/;"    v    typeref:typename:float
+alpha    config.h    /^float alpha = 0.92;$/;"    v    typeref:typename:float
+arg.h    arg.h    1;"    F    epoch:1667356258
+argv0    x.c    /^char *argv0;$/;"    v    typeref:typename:char *
+ascii_printable    config.def.h    /^static char ascii_printable[] =$/;"    v    typeref:typename:char[]
+ascii_printable    config.h    /^static char ascii_printable[] =$/;"    v    typeref:typename:char[]
+base64dec    st.c    /^base64dec(const char *src)$/;"    f    typeref:typename:char *
+base64dec_getc    st.c    /^base64dec_getc(const char **src)$/;"    f    typeref:typename:char
+bellvolume    config.def.h    /^static int bellvolume = 0;$/;"    v    typeref:typename:int
+bellvolume    config.h    /^static int bellvolume = 0;$/;"    v    typeref:typename:int
+bg    config.def.h    /^    unsigned int bg;               \/* background *\/$/;"    m    struct:__anon9258968e0108    typeref:typename:unsigned int
+bg    config.h    /^    unsigned int bg;               \/* background *\/$/;"    m    struct:__anon41b17d910108    typeref:typename:unsigned int
+bg    st.h    /^    uint32_t bg;      \/* background  *\/$/;"    m    struct:__anon7c9e12e20108    typeref:typename:uint32_t
+blinktimeout    config.def.h    /^static unsigned int blinktimeout = 800;$/;"    v    typeref:typename:unsigned int
+blinktimeout    config.h    /^static unsigned int blinktimeout = 800;$/;"    v    typeref:typename:unsigned int
+bmotion    x.c    /^bmotion(XEvent *e)$/;"    f    typeref:typename:void
+borderpx    config.def.h    /^static int borderpx = 0;$/;"    v    typeref:typename:int
+borderpx    config.h    /^static int borderpx = 0;$/;"    v    typeref:typename:int
+bpress    x.c    /^bpress(XEvent *e)$/;"    f    typeref:typename:void
+brelease    x.c    /^brelease(XEvent *e)$/;"    f    typeref:typename:void
+buttonmask    x.c    /^buttonmask(uint button)$/;"    f    typeref:typename:uint
+chscale    config.def.h    /^static float chscale = 1.0;$/;"    v    typeref:typename:float
+chscale    config.h    /^static float chscale = 1.0;$/;"    v    typeref:typename:float
+clean    Makefile    /^clean:$/;"    t
+clipcopy    x.c    /^clipcopy(const Arg *dummy)$/;"    f    typeref:typename:void
+clippaste    x.c    /^clippaste(const Arg *dummy)$/;"    f    typeref:typename:void
+cmessage    x.c    /^cmessage(XEvent *e)$/;"    f    typeref:typename:void
+colorname    config.def.h    /^static const char * const * colorname;$/;"    v    typeref:typename:const char * const *
+colorname    config.h    /^static const char * const * colorname;$/;"    v    typeref:typename:const char * const *
+colors    config.def.h    /^    const char* const colors[258]; \/* terminal colors *\/$/;"    m    struct:__anon9258968e0108    typeref:typename:const char * const[258]
+colors    config.h    /^    const char* const colors[258]; \/* terminal colors *\/$/;"    m    struct:__anon41b17d910108    typeref:typename:const char * const[258]
+colorscheme    config.def.h    /^int colorscheme = 0;$/;"    v    typeref:typename:int
+colorscheme    config.h    /^int colorscheme = 0;$/;"    v    typeref:typename:int
+cols    config.def.h    /^static unsigned int cols = 80;$/;"    v    typeref:typename:unsigned int
+cols    config.h    /^static unsigned int cols = 80;$/;"    v    typeref:typename:unsigned int
+config.def.h    config.def.h    1;"    F    epoch:1667356965
+config.h    Makefile    /^config.h:$/;"    t
+config.h    config.h    1;"    F    epoch:1667356753
+config.mk    config.mk    1;"    F    epoch:1667356258
+cresize    x.c    /^cresize(int width, int height)$/;"    f    typeref:typename:void
+cs    config.def.h    /^    unsigned int cs;               \/* cursor *\/$/;"    m    struct:__anon9258968e0108    typeref:typename:unsigned int
+cs    config.h    /^    unsigned int cs;               \/* cursor *\/$/;"    m    struct:__anon41b17d910108    typeref:typename:unsigned int
+csidump    st.c    /^csidump(void)$/;"    f    typeref:typename:void
+csihandle    st.c    /^csihandle(void)$/;"    f    typeref:typename:void
+csiparse    st.c    /^csiparse(void)$/;"    f    typeref:typename:void
+csireset    st.c    /^csireset(void)$/;"    f    typeref:typename:void
+cursorstyle    config.def.h    /^static unsigned int cursorstyle = 1;$/;"    v    typeref:typename:unsigned int
+cursorstyle    config.h    /^static unsigned int cursorstyle = 1;$/;"    v    typeref:typename:unsigned int
+cursorthickness    config.def.h    /^static unsigned int cursorthickness = 2;$/;"    v    typeref:typename:unsigned int
+cursorthickness    config.h    /^static unsigned int cursorthickness = 2;$/;"    v    typeref:typename:unsigned int
+cwscale    config.def.h    /^static float cwscale = 1.05;$/;"    v    typeref:typename:float
+cwscale    config.h    /^static float cwscale = 1.05;$/;"    v    typeref:typename:float
+defaultattr    config.def.h    /^static unsigned int defaultattr = 11;$/;"    v    typeref:typename:unsigned int
+defaultattr    config.h    /^static unsigned int defaultattr = 11;$/;"    v    typeref:typename:unsigned int
+defaultbg    config.def.h    /^unsigned int defaultbg;$/;"    v    typeref:typename:unsigned int
+defaultbg    config.h    /^unsigned int defaultbg;$/;"    v    typeref:typename:unsigned int
+defaultcs    config.def.h    /^unsigned int defaultcs;$/;"    v    typeref:typename:unsigned int
+defaultcs    config.h    /^unsigned int defaultcs;$/;"    v    typeref:typename:unsigned int
+defaultfg    config.def.h    /^unsigned int defaultfg;$/;"    v    typeref:typename:unsigned int
+defaultfg    config.h    /^unsigned int defaultfg;$/;"    v    typeref:typename:unsigned int
+defaultrcs    config.def.h    /^static unsigned int defaultrcs;$/;"    v    typeref:typename:unsigned int
+defaultrcs    config.h    /^static unsigned int defaultrcs;$/;"    v    typeref:typename:unsigned int
+die    st.c    /^die(const char *errstr, ...)$/;"    f    typeref:typename:void
+dist    Makefile    /^dist: clean$/;"    t
+doubleclicktimeout    config.def.h    /^static unsigned int doubleclicktimeout = 300;$/;"    v    typeref:typename:unsigned int
+doubleclicktimeout    config.h    /^static unsigned int doubleclicktimeout = 300;$/;"    v    typeref:typename:unsigned int
+draw    st.c    /^draw(void)$/;"    f    typeref:typename:void
+drawregion    st.c    /^drawregion(int x1, int y1, int x2, int y2)$/;"    f    typeref:typename:void
+eschandle    st.c    /^eschandle(uchar ascii)$/;"    f    typeref:typename:int
+evcol    x.c    /^evcol(XEvent *e)$/;"    f    typeref:typename:int
+evrow    x.c    /^evrow(XEvent *e)$/;"    f    typeref:typename:int
+execsh    st.c    /^execsh(char *cmd, char **args)$/;"    f    typeref:typename:void
+expose    x.c    /^expose(XEvent *ev)$/;"    f    typeref:typename:void
+f    st.h    /^    float f;$/;"    m    union:__anon7c9e12e2020a    typeref:typename:float
+fg    config.def.h    /^    unsigned int fg;               \/* foreground *\/$/;"    m    struct:__anon9258968e0108    typeref:typename:unsigned int
+fg    config.h    /^    unsigned int fg;               \/* foreground *\/$/;"    m    struct:__anon41b17d910108    typeref:typename:unsigned int
+fg    st.h    /^    uint32_t fg;      \/* foreground  *\/$/;"    m    struct:__anon7c9e12e20108    typeref:typename:uint32_t
+focus    x.c    /^focus(XEvent *ev)$/;"    f    typeref:typename:void
+font    config.def.h    /^static char *font = "Victor Mono:style=Regular:size=10:antialias=true:autohint=true";$/;"    v    typeref:typename:char *
+font    config.h    /^static char *font = "Victor Mono:style=Regular:size=10:antialias=true:autohint=true";$/;"    v    typeref:typename:char *
+forcemousemod    config.def.h    /^static uint forcemousemod = ShiftMask;$/;"    v    typeref:typename:uint
+forcemousemod    config.h    /^static uint forcemousemod = ShiftMask;$/;"    v    typeref:typename:uint
+getsel    st.c    /^getsel(void)$/;"    f    typeref:typename:char *
+glyph_attribute    st.h    /^enum glyph_attribute {$/;"    g
+i    st.h    /^    int i;$/;"    m    union:__anon7c9e12e2020a    typeref:typename:int
+ignoremod    config.def.h    /^static uint ignoremod = Mod2Mask|XK_SWITCH_MOD;$/;"    v    typeref:typename:uint
+ignoremod    config.h    /^static uint ignoremod = Mod2Mask|XK_SWITCH_MOD;$/;"    v    typeref:typename:uint
+install    Makefile    /^install: st$/;"    t
+key    config.def.h    /^static Key key[] = {$/;"    v    typeref:typename:Key[]
+key    config.h    /^static Key key[] = {$/;"    v    typeref:typename:Key[]
+kmap    x.c    /^kmap(KeySym k, uint state)$/;"    f    typeref:typename:char *
+kpress    x.c    /^kpress(XEvent *ev)$/;"    f    typeref:typename:void
+kscrolldown    st.c    /^kscrolldown(const Arg* a)$/;"    f    typeref:typename:void
+kscrollup    st.c    /^kscrollup(const Arg* a)$/;"    f    typeref:typename:void
+main    x.c    /^main(int argc, char *argv[])$/;"    f    typeref:typename:int
+mappedkeys    config.def.h    /^static KeySym mappedkeys[] = { -1 };$/;"    v    typeref:typename:KeySym[]
+mappedkeys    config.h    /^static KeySym mappedkeys[] = { -1 };$/;"    v    typeref:typename:KeySym[]
+match    x.c    /^match(uint mask, uint state)$/;"    f    typeref:typename:int
+maxlatency    config.def.h    /^static double maxlatency = 33;$/;"    v    typeref:typename:double
+maxlatency    config.h    /^static double maxlatency = 33;$/;"    v    typeref:typename:double
+minlatency    config.def.h    /^static double minlatency = 8;$/;"    v    typeref:typename:double
+minlatency    config.h    /^static double minlatency = 8;$/;"    v    typeref:typename:double
+mode    st.h    /^    ushort mode;      \/* attribute flags *\/$/;"    m    struct:__anon7c9e12e20108    typeref:typename:ushort
+mouseaction    x.c    /^mouseaction(XEvent *e, uint release)$/;"    f    typeref:typename:int
+mousebg    config.def.h    /^static unsigned int mousebg = 0;$/;"    v    typeref:typename:unsigned int
+mousebg    config.h    /^static unsigned int mousebg = 0;$/;"    v    typeref:typename:unsigned int
+mousefg    config.def.h    /^static unsigned int mousefg = 7;$/;"    v    typeref:typename:unsigned int
+mousefg    config.h    /^static unsigned int mousefg = 7;$/;"    v    typeref:typename:unsigned int
+mousereport    x.c    /^mousereport(XEvent *e)$/;"    f    typeref:typename:void
+mousesel    x.c    /^mousesel(XEvent *e, int done)$/;"    f    typeref:typename:void
+mouseshape    config.def.h    /^static unsigned int mouseshape = XC_xterm;$/;"    v    typeref:typename:unsigned int
+mouseshape    config.h    /^static unsigned int mouseshape = XC_xterm;$/;"    v    typeref:typename:unsigned int
+mshortcuts    config.def.h    /^static MouseShortcut mshortcuts[] = {$/;"    v    typeref:typename:MouseShortcut[]
+mshortcuts    config.h    /^static MouseShortcut mshortcuts[] = {$/;"    v    typeref:typename:MouseShortcut[]
+nextscheme    x.c    /^nextscheme(const Arg *arg)$/;"    f    typeref:typename:void
+numlock    x.c    /^numlock(const Arg *dummy)$/;"    f    typeref:typename:void
+options    Makefile    /^options:$/;"    t
+osc_color_response    st.c    /^osc_color_response(int num, int index, int is_osc4)$/;"    f    typeref:typename:void
+printscreen    st.c    /^printscreen(const Arg *arg)$/;"    f    typeref:typename:void
+printsel    st.c    /^printsel(const Arg *arg)$/;"    f    typeref:typename:void
+propnotify    x.c    /^propnotify(XEvent *e)$/;"    f    typeref:typename:void
+rcs    config.def.h    /^    unsigned int rcs;              \/* reverse cursor *\/$/;"    m    struct:__anon9258968e0108    typeref:typename:unsigned int
+rcs    config.h    /^    unsigned int rcs;              \/* reverse cursor *\/$/;"    m    struct:__anon41b17d910108    typeref:typename:unsigned int
+redraw    st.c    /^redraw(void)$/;"    f    typeref:typename:void
+resettitle    st.c    /^resettitle(void)$/;"    f    typeref:typename:void
+resize    x.c    /^resize(XEvent *e)$/;"    f    typeref:typename:void
+rows    config.def.h    /^static unsigned int rows = 24;$/;"    v    typeref:typename:unsigned int
+rows    config.h    /^static unsigned int rows = 24;$/;"    v    typeref:typename:unsigned int
+run    x.c    /^run(void)$/;"    f    typeref:typename:void
+s    st.h    /^    const char *s;$/;"    m    union:__anon7c9e12e2020a    typeref:typename:const char *
+scheme    config.def.h    /^static void scheme = 0;$/;"    v    typeref:typename:void
+scheme    config.h    /^static unsigned char scheme = 0;$/;"    v    typeref:typename:unsigned char
+schemes    config.def.h    /^static const ColorScheme schemes[] = {$/;"    v    typeref:typename:const ColorScheme[]
+schemes    config.h    /^static const ColorScheme schemes[] = {$/;"    v    typeref:typename:const ColorScheme[]
+scroll    config.def.h    /^char *scroll = NULL;$/;"    v    typeref:typename:char *
+scroll    config.h    /^char *scroll = NULL;$/;"    v    typeref:typename:char *
+selclear    st.c    /^selclear(void)$/;"    f    typeref:typename:void
+selclear_    x.c    /^selclear_(XEvent *e)$/;"    f    typeref:typename:void
+selected    st.c    /^selected(int x, int y)$/;"    f    typeref:typename:int
+selection_mode    st.h    /^enum selection_mode {$/;"    g
+selection_snap    st.h    /^enum selection_snap {$/;"    g
+selection_type    st.h    /^enum selection_type {$/;"    g
+selectscheme    x.c    /^selectscheme(const Arg *arg)$/;"    f    typeref:typename:void
+selextend    st.c    /^selextend(int col, int row, int type, int done)$/;"    f    typeref:typename:void
+selinit    st.c    /^selinit(void)$/;"    f    typeref:typename:void
+selmasks    config.def.h    /^static uint selmasks[] = {$/;"    v    typeref:typename:uint[]
+selmasks    config.h    /^static uint selmasks[] = {$/;"    v    typeref:typename:uint[]
+selnormalize    st.c    /^selnormalize(void)$/;"    f    typeref:typename:void
+selnotify    x.c    /^selnotify(XEvent *e)$/;"    f    typeref:typename:void
+selpaste    x.c    /^selpaste(const Arg *dummy)$/;"    f    typeref:typename:void
+selrequest    x.c    /^selrequest(XEvent *e)$/;"    f    typeref:typename:void
+selscroll    st.c    /^selscroll(int orig, int n)$/;"    f    typeref:typename:void
+selsnap    st.c    /^selsnap(int *x, int *y, int direction)$/;"    f    typeref:typename:void
+selstart    st.c    /^selstart(int col, int row, int snap)$/;"    f    typeref:typename:void
+sendbreak    st.c    /^sendbreak(const Arg *arg)$/;"    f    typeref:typename:void
+setsel    x.c    /^setsel(char *str, Time t)$/;"    f    typeref:typename:void
+shell    config.def.h    /^static char *shell = "\/bin\/sh";$/;"    v    typeref:typename:char *
+shell    config.h    /^static char *shell = "\/bin\/sh";$/;"    v    typeref:typename:char *
+shortcuts    config.def.h    /^static Shortcut shortcuts[] = {$/;"    v    typeref:typename:Shortcut[]
+shortcuts    config.h    /^static Shortcut shortcuts[] = {$/;"    v    typeref:typename:Shortcut[]
+sigchld    st.c    /^sigchld(int a)$/;"    f    typeref:typename:void
+sixd_to_16bit    x.c    /^sixd_to_16bit(int x)$/;"    f    typeref:typename:ushort
+st    Makefile    /^st: $(OBJ)$/;"    t
+st-alpha-20220206-0.8.5.diff    patches/st-alpha-20220206-0.8.5.diff    1;"    F    epoch:1667356258
+st-anysize-20220718-baa9357.diff    patches/st-anysize-20220718-baa9357.diff    1;"    F    epoch:1667356258
+st-blinking_cursor-20211116-2f6e597.diff    patches/st-blinking_cursor-20211116-2f6e597.diff    1;"    F    epoch:1667356258
+st-colorschemes-0.8.5.diff    patches/st-colorschemes-0.8.5.diff    1;"    F    epoch:1667356324
+st-gruvbox-dark-0.8.5.diff    patches/st-gruvbox-dark-0.8.5.diff    1;"    F    epoch:1667356258
+st-scrollback-20210507-4536f46.diff    patches/st-scrollback-20210507-4536f46.diff    1;"    F    epoch:1667356258
+st-scrollback-mouse-20220127-2c5edf2.diff    patches/st-scrollback-mouse-20220127-2c5edf2.diff    1;"    F    epoch:1667356258
+st-scrollback-reflow-0.8.5.diff    patches/st-scrollback-reflow-0.8.5.diff    1;"    F    epoch:1667356258
+st-scrollback-ringbuffer-0.8.5.diff    patches/st-scrollback-ringbuffer-0.8.5.diff    1;"    F    epoch:1667356258
+st-universcroll-0.8.4.diff    patches/st-universcroll-0.8.4.diff    1;"    F    epoch:1667356258
+st.1    st.1    1;"    F    epoch:1667356258
+st.c    st.c    1;"    F    epoch:1667356329
+st.h    st.h    1;"    F    epoch:1667356329
+st.o    Makefile    /^st.o: config.h st.h win.h$/;"    t
+stcursor    config.def.h    /^static Rune stcursor = 0x2603; \/* snowman ("☃") *\/$/;"    v    typeref:typename:Rune
+stcursor    config.h    /^static Rune stcursor = 0x2603; \/* snowman ("☃") *\/$/;"    v    typeref:typename:Rune
+strdump    st.c    /^strdump(void)$/;"    f    typeref:typename:void
+strhandle    st.c    /^strhandle(void)$/;"    f    typeref:typename:void
+strparse    st.c    /^strparse(void)$/;"    f    typeref:typename:void
+strreset    st.c    /^strreset(void)$/;"    f    typeref:typename:void
+stty    st.c    /^stty(char **args)$/;"    f    typeref:typename:void
+stty_args    config.def.h    /^char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400";$/;"    v    typeref:typename:char *
+stty_args    config.h    /^char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400";$/;"    v    typeref:typename:char *
+tabspaces    config.def.h    /^unsigned int tabspaces = 8;$/;"    v    typeref:typename:unsigned int
+tabspaces    config.h    /^unsigned int tabspaces = 8;$/;"    v    typeref:typename:unsigned int
+tattrset    st.c    /^tattrset(int attr)$/;"    f    typeref:typename:int
+tclearregion    st.c    /^tclearregion(int x1, int y1, int x2, int y2)$/;"    f    typeref:typename:void
+tcontrolcode    st.c    /^tcontrolcode(uchar ascii)$/;"    f    typeref:typename:void
+tcursor    st.c    /^tcursor(int mode)$/;"    f    typeref:typename:void
+tdectest    st.c    /^tdectest(char c)$/;"    f    typeref:typename:void
+tdefcolor    st.c    /^tdefcolor(const int *attr, int *npar, int l)$/;"    f    typeref:typename:int32_t
+tdeftran    st.c    /^tdeftran(char ascii)$/;"    f    typeref:typename:void
+tdefutf8    st.c    /^tdefutf8(char ascii)$/;"    f    typeref:typename:void
+tdeletechar    st.c    /^tdeletechar(int n)$/;"    f    typeref:typename:void
+tdeleteline    st.c    /^tdeleteline(int n)$/;"    f    typeref:typename:void
+tdump    st.c    /^tdump(void)$/;"    f    typeref:typename:void
+tdumpline    st.c    /^tdumpline(int n)$/;"    f    typeref:typename:void
+tdumpsel    st.c    /^tdumpsel(void)$/;"    f    typeref:typename:void
+termname    config.def.h    /^char *termname = "st-256color";$/;"    v    typeref:typename:char *
+termname    config.h    /^char *termname = "st-256color";$/;"    v    typeref:typename:char *
+tfulldirt    st.c    /^tfulldirt(void)$/;"    f    typeref:typename:void
+tinsertblank    st.c    /^tinsertblank(int n)$/;"    f    typeref:typename:void
+tinsertblankline    st.c    /^tinsertblankline(int n)$/;"    f    typeref:typename:void
+tisaltscr    st.c    /^int tisaltscr(void)$/;"    f    typeref:typename:int
+tlinelen    st.c    /^tlinelen(int y)$/;"    f    typeref:typename:int
+tmoveato    st.c    /^tmoveato(int x, int y)$/;"    f    typeref:typename:void
+tmoveto    st.c    /^tmoveto(int x, int y)$/;"    f    typeref:typename:void
+tnew    st.c    /^tnew(int col, int row)$/;"    f    typeref:typename:void
+tnewline    st.c    /^tnewline(int first_col)$/;"    f    typeref:typename:void
+toggleprinter    st.c    /^toggleprinter(const Arg *arg)$/;"    f    typeref:typename:void
+togglescheme    config.def.h    /^static void togglescheme() {$/;"    f    typeref:typename:void
+togglescheme    config.h    /^static unsigned char togglescheme() {$/;"    f    typeref:typename:unsigned char
+tprinter    st.c    /^tprinter(char *s, size_t len)$/;"    f    typeref:typename:void
+tputc    st.c    /^tputc(Rune u)$/;"    f    typeref:typename:void
+tputtab    st.c    /^tputtab(int n)$/;"    f    typeref:typename:void
+treset    st.c    /^treset(void)$/;"    f    typeref:typename:void
+tresize    st.c    /^tresize(int col, int row)$/;"    f    typeref:typename:void
+tripleclicktimeout    config.def.h    /^static unsigned int tripleclicktimeout = 600;$/;"    v    typeref:typename:unsigned int
+tripleclicktimeout    config.h    /^static unsigned int tripleclicktimeout = 600;$/;"    v    typeref:typename:unsigned int
+tscrolldown    st.c    /^tscrolldown(int orig, int n, int copyhist)$/;"    f    typeref:typename:void
+tscrollup    st.c    /^tscrollup(int orig, int n, int copyhist)$/;"    f    typeref:typename:void
+tsetattr    st.c    /^tsetattr(const int *attr, int l)$/;"    f    typeref:typename:void
+tsetchar    st.c    /^tsetchar(Rune u, const Glyph *attr, int x, int y)$/;"    f    typeref:typename:void
+tsetdirt    st.c    /^tsetdirt(int top, int bot)$/;"    f    typeref:typename:void
+tsetdirtattr    st.c    /^tsetdirtattr(int attr)$/;"    f    typeref:typename:void
+tsetmode    st.c    /^tsetmode(int priv, int set, const int *args, int narg)$/;"    f    typeref:typename:void
+tsetscroll    st.c    /^tsetscroll(int t, int b)$/;"    f    typeref:typename:void
+tstrsequence    st.c    /^tstrsequence(uchar c)$/;"    f    typeref:typename:void
+tswapscreen    st.c    /^tswapscreen(void)$/;"    f    typeref:typename:void
+ttyhangup    st.c    /^ttyhangup(void)$/;"    f    typeref:typename:void
+ttynew    st.c    /^ttynew(const char *line, char *cmd, const char *out, char **args)$/;"    f    typeref:typename:int
+ttyread    st.c    /^ttyread(void)$/;"    f    typeref:typename:size_t
+ttyresize    st.c    /^ttyresize(int tw, int th)$/;"    f    typeref:typename:void
+ttysend    x.c    /^ttysend(const Arg *arg)$/;"    f    typeref:typename:void
+ttywrite    st.c    /^ttywrite(const char *s, size_t n, int may_echo)$/;"    f    typeref:typename:void
+ttywriteraw    st.c    /^ttywriteraw(const char *s, size_t n)$/;"    f    typeref:typename:void
+tupdatebgcolor    st.c    /^tupdatebgcolor(int oldbg, int newbg)$/;"    f    typeref:typename:void
+tupdatefgcolor    st.c    /^tupdatefgcolor(int oldfg, int newfg)$/;"    f    typeref:typename:void
+twrite    st.c    /^twrite(const char *buf, int buflen, int show_ctrl)$/;"    f    typeref:typename:int
+u    st.h    /^    Rune u;           \/* character code *\/$/;"    m    struct:__anon7c9e12e20108    typeref:typename:Rune
+uchar    st.h    /^typedef unsigned char uchar;$/;"    t    typeref:typename:unsigned char
+ui    st.h    /^    uint ui;$/;"    m    union:__anon7c9e12e2020a    typeref:typename:uint
+uint    st.h    /^typedef unsigned int uint;$/;"    t    typeref:typename:unsigned int
+ulong    st.h    /^typedef unsigned long ulong;$/;"    t    typeref:typename:unsigned long
+uninstall    Makefile    /^uninstall:$/;"    t
+unmap    x.c    /^unmap(XEvent *ev)$/;"    f    typeref:typename:void
+updatescheme    x.c    /^updatescheme(void)$/;"    f    typeref:typename:void
+usage    x.c    /^usage(void)$/;"    f    typeref:typename:void
+ushort    st.h    /^typedef unsigned short ushort;$/;"    t    typeref:typename:unsigned short
+utf8decode    st.c    /^utf8decode(const char *c, Rune *u, size_t clen)$/;"    f    typeref:typename:size_t
+utf8decodebyte    st.c    /^utf8decodebyte(char c, size_t *i)$/;"    f    typeref:typename:Rune
+utf8encode    st.c    /^utf8encode(Rune u, char *c)$/;"    f    typeref:typename:size_t
+utf8encodebyte    st.c    /^utf8encodebyte(Rune u, size_t i)$/;"    f    typeref:typename:char
+utf8validate    st.c    /^utf8validate(Rune *u, size_t i)$/;"    f    typeref:typename:size_t
+utmp    config.def.h    /^char *utmp = NULL;$/;"    v    typeref:typename:char *
+utmp    config.h    /^char *utmp = NULL;$/;"    v    typeref:typename:char *
+v    st.h    /^    const void *v;$/;"    m    union:__anon7c9e12e2020a    typeref:typename:const void *
+visibility    x.c    /^visibility(XEvent *ev)$/;"    f    typeref:typename:void
+vtiden    config.def.h    /^char *vtiden = "\\033[?6c";$/;"    v    typeref:typename:char *
+vtiden    config.h    /^char *vtiden = "\\033[?6c";$/;"    v    typeref:typename:char *
+win.h    win.h    1;"    F    epoch:1667356258
+win_mode    win.h    /^enum win_mode {$/;"    g
+worddelimiters    config.def.h    /^wchar_t *worddelimiters = L" ";$/;"    v    typeref:typename:wchar_t *
+worddelimiters    config.h    /^wchar_t *worddelimiters = L" ";$/;"    v    typeref:typename:wchar_t *
+x.c    x.c    1;"    F    epoch:1667356911
+x.o    Makefile    /^x.o: arg.h config.h st.h win.h$/;"    t
+xbell    x.c    /^xbell(void)$/;"    f    typeref:typename:void
+xclear    x.c    /^xclear(int x1, int y1, int x2, int y2)$/;"    f    typeref:typename:void
+xclipcopy    x.c    /^xclipcopy(void)$/;"    f    typeref:typename:void
+xdrawcursor    x.c    /^xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)$/;"    f    typeref:typename:void
+xdrawglyph    x.c    /^xdrawglyph(Glyph g, int x, int y)$/;"    f    typeref:typename:void
+xdrawglyphfontspecs    x.c    /^xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)$/;"    f    typeref:typename:void
+xdrawline    x.c    /^xdrawline(Line line, int x1, int y1, int x2)$/;"    f    typeref:typename:void
+xfinishdraw    x.c    /^xfinishdraw(void)$/;"    f    typeref:typename:void
+xgeommasktogravity    x.c    /^xgeommasktogravity(int mask)$/;"    f    typeref:typename:int
+xgetcolor    x.c    /^xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b)$/;"    f    typeref:typename:int
+xhints    x.c    /^xhints(void)$/;"    f    typeref:typename:void
+xicdestroy    x.c    /^xicdestroy(XIC xim, XPointer client, XPointer call)$/;"    f    typeref:typename:int
+ximdestroy    x.c    /^ximdestroy(XIM xim, XPointer client, XPointer call)$/;"    f    typeref:typename:void
+ximinstantiate    x.c    /^ximinstantiate(Display *dpy, XPointer client, XPointer call)$/;"    f    typeref:typename:void
+ximopen    x.c    /^ximopen(Display *dpy)$/;"    f    typeref:typename:int
+xinit    x.c    /^xinit(int cols, int rows)$/;"    f    typeref:typename:void
+xloadcolor    x.c    /^xloadcolor(int i, const char *name, Color *ncolor)$/;"    f    typeref:typename:int
+xloadcols    x.c    /^xloadcols(void)$/;"    f    typeref:typename:void
+xloadfont    x.c    /^xloadfont(Font *f, FcPattern *pattern)$/;"    f    typeref:typename:int
+xloadfonts    x.c    /^xloadfonts(const char *fontstr, double fontsize)$/;"    f    typeref:typename:void
+xmakeglyphfontspecs    x.c    /^xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)$/;"    f    typeref:typename:int
+xmalloc    st.c    /^xmalloc(size_t len)$/;"    f    typeref:typename:void *
+xrealloc    st.c    /^xrealloc(void *p, size_t len)$/;"    f    typeref:typename:void *
+xresize    x.c    /^xresize(int col, int row)$/;"    f    typeref:typename:void
+xsetcolorname    x.c    /^xsetcolorname(int x, const char *name)$/;"    f    typeref:typename:int
+xsetcursor    x.c    /^xsetcursor(int cursor)$/;"    f    typeref:typename:int
+xsetenv    x.c    /^xsetenv(void)$/;"    f    typeref:typename:void
+xseticontitle    x.c    /^xseticontitle(char *p)$/;"    f    typeref:typename:void
+xsetmode    x.c    /^xsetmode(int set, unsigned int flags)$/;"    f    typeref:typename:void
+xsetpointermotion    x.c    /^xsetpointermotion(int set)$/;"    f    typeref:typename:void
+xsetsel    x.c    /^xsetsel(char *str)$/;"    f    typeref:typename:void
+xsettitle    x.c    /^xsettitle(char *p)$/;"    f    typeref:typename:void
+xseturgency    x.c    /^xseturgency(int add)$/;"    f    typeref:typename:void
+xstartdraw    x.c    /^xstartdraw(void)$/;"    f    typeref:typename:int
+xstrdup    st.c    /^xstrdup(const char *s)$/;"    f    typeref:typename:char *
+xunloadfont    x.c    /^xunloadfont(Font *f)$/;"    f    typeref:typename:void
+xunloadfonts    x.c    /^xunloadfonts(void)$/;"    f    typeref:typename:void
+xwrite    st.c    /^xwrite(int fd, const char *s, size_t len)$/;"    f    typeref:typename:ssize_t
+xximspot    x.c    /^xximspot(int x, int y)$/;"    f    typeref:typename:void
+zoom    x.c    /^zoom(const Arg *arg)$/;"    f    typeref:typename:void
+zoomabs    x.c    /^zoomabs(const Arg *arg)$/;"    f    typeref:typename:void
+zoomreset    x.c    /^zoomreset(const Arg *arg)$/;"    f    typeref:typename:void
diff --git a/x.c b/x.c
@@ -34,6 +34,7 @@ typedef struct {
     void (*func)(const Arg *);
     const Arg arg;
     uint  release;
+    int  altscrn;  /* 0: don't care, -1: not alt screen, 1: alt screen */
 } MouseShortcut;
 
 typedef struct {
@@ -59,6 +60,8 @@ static void zoom(const Arg *);
 static void zoomabs(const Arg *);
 static void zoomreset(const Arg *);
 static void ttysend(const Arg *);
+static void nextscheme(const Arg *);
+static void selectscheme(const Arg *);
 
 /* config.h for applying patches and the configuration. */
 #include "config.h"
@@ -81,6 +84,7 @@ typedef XftGlyphFontSpec GlyphFontSpec;
 typedef struct {
     int tw, th; /* tty width and height */
     int w, h; /* window width and height */
+    int hborderpx, vborderpx;
     int ch; /* char height */
     int cw; /* char width  */
     int mode; /* window state/mode flags */
@@ -186,6 +190,7 @@ static void mousesel(XEvent *, int);
 static void mousereport(XEvent *);
 static char *kmap(KeySym, uint);
 static int match(uint, uint);
+static void updatescheme(void);
 
 static void run(void);
 static void usage(void);
@@ -255,6 +260,7 @@ static char *opt_name  = NULL;
 static char *opt_title = NULL;
 
 static uint buttons; /* bit field of pressed buttons */
+static int cursorblinks = 0;
 
 void
 clipcopy(const Arg *dummy)
@@ -333,7 +339,7 @@ ttysend(const Arg *arg)
 int
 evcol(XEvent *e)
 {
-    int x = e->xbutton.x - borderpx;
+    int x = e->xbutton.x - win.hborderpx;
     LIMIT(x, 0, win.tw - 1);
     return x / win.cw;
 }
@@ -341,7 +347,7 @@ evcol(XEvent *e)
 int
 evrow(XEvent *e)
 {
-    int y = e->xbutton.y - borderpx;
+    int y = e->xbutton.y - win.vborderpx;
     LIMIT(y, 0, win.th - 1);
     return y / win.ch;
 }
@@ -457,6 +463,7 @@ mouseaction(XEvent *e, uint release)
     for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
         if (ms->release == release &&
             ms->button == e->xbutton.button &&
+            (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) &&
             (match(ms->mod, state) ||  /* exact or forced */
              match(ms->mod, state & ~forcemousemod))) {
             ms->func(&(ms->arg));
@@ -741,6 +748,9 @@ cresize(int width, int height)
     col = MAX(1, col);
     row = MAX(1, row);
 
+    win.hborderpx = (win.w - col * win.cw) / 2;
+    win.vborderpx = (win.h - row * win.ch) / 2;
+
     tresize(col, row);
     xresize(col, row);
     ttyresize(win.tw, win.th);
@@ -803,7 +813,7 @@ xloadcols(void)
         for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp)
             XftColorFree(xw.dpy, xw.vis, xw.cmap, cp);
     } else {
-        dc.collen = MAX(LEN(colorname), 256);
+        dc.collen = 258;
         dc.col = xmalloc(dc.collen * sizeof(Color));
     }
 
@@ -878,8 +888,8 @@ xhints(void)
     sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
     sizeh->height = win.h;
     sizeh->width = win.w;
-    sizeh->height_inc = win.ch;
-    sizeh->width_inc = win.cw;
+    sizeh->height_inc = 1;
+    sizeh->width_inc = 1;
     sizeh->base_height = 2 * borderpx;
     sizeh->base_width = 2 * borderpx;
     sizeh->min_height = win.ch + 2 * borderpx;
@@ -1173,8 +1183,8 @@ xinit(int cols, int rows)
     xloadcols();
 
     /* adjust fixed window geometry */
-    win.w = 2 * borderpx + cols * win.cw;
-    win.h = 2 * borderpx + rows * win.ch;
+    win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw;
+    win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch;
     if (xw.gm & XNegative)
         xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
     if (xw.gm & YNegative)
@@ -1259,7 +1269,7 @@ xinit(int cols, int rows)
 int
 xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
 {
-    float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
+    float winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, xp, yp;
     ushort mode, prevmode = USHRT_MAX;
     Font *font = &dc.font;
     int frcflags = FRC_NORMAL;
@@ -1392,7 +1402,7 @@ void
 xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
 {
     int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
-    int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
+    int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch,
         width = charlen * win.cw;
     Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
     XRenderColor colfg, colbg;
@@ -1482,17 +1492,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 
     /* Intelligent cleaning up of the borders. */
     if (x == 0) {
-        xclear(0, (y == 0)? 0 : winy, borderpx,
+        xclear(0, (y == 0)? 0 : winy, win.hborderpx,
             winy + win.ch +
-            ((winy + win.ch >= borderpx + win.th)? win.h : 0));
+            ((winy + win.ch >= win.vborderpx + win.th)? win.h : 0));
     }
-    if (winx + width >= borderpx + win.tw) {
+    if (winx + width >= win.hborderpx + win.tw) {
         xclear(winx + width, (y == 0)? 0 : winy, win.w,
-            ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch)));
+            ((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch)));
     }
     if (y == 0)
-        xclear(winx, 0, winx + width, borderpx);
-    if (winy + win.ch >= borderpx + win.th)
+        xclear(winx, 0, winx + width, win.vborderpx);
+    if (winy + win.ch >= win.vborderpx + win.th)
         xclear(winx, winy + win.ch, winx + width, win.h);
 
     /* Clean up the region we want to draw to. */
@@ -1575,46 +1585,60 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
     /* draw the new one */
     if (IS_SET(MODE_FOCUSED)) {
         switch (win.cursor) {
-        case 7: /* st extension */
-            g.u = 0x2603; /* snowman (U+2603) */
-            /* FALLTHROUGH */
-        case 0: /* Blinking Block */
-        case 1: /* Blinking Block (Default) */
+        default:
+        case 0: /* blinking block */
+        case 1: /* blinking block (default) */
+            if (IS_SET(MODE_BLINK))
+                break;
         case 2: /* Steady Block */
             xdrawglyph(g, cx, cy);
             break;
-        case 3: /* Blinking Underline */
-        case 4: /* Steady Underline */
+        case 3: /* blinking underline */
+            if (IS_SET(MODE_BLINK))
+                break;
+            /* FALLTHROUGH */
+        case 4: /* steady underline */
             XftDrawRect(xw.draw, &drawcol,
-                    borderpx + cx * win.cw,
-                    borderpx + (cy + 1) * win.ch - \
+                    win.hborderpx + cx * win.cw,
+                    win.vborderpx + (cy + 1) * win.ch - \
                         cursorthickness,
                     win.cw, cursorthickness);
             break;
-        case 5: /* Blinking bar */
-        case 6: /* Steady bar */
+        case 5: /* blinking bar */
+            if (IS_SET(MODE_BLINK))
+                break;
+            /* FALLTHROUGH */
+        case 6: /* steady bar */
             XftDrawRect(xw.draw, &drawcol,
-                    borderpx + cx * win.cw,
-                    borderpx + cy * win.ch,
+                    win.hborderpx + cx * win.cw,
+                    win.vborderpx + cy * win.ch,
                     cursorthickness, win.ch);
             break;
+        case 7: /* blinking st cursor */
+            if (IS_SET(MODE_BLINK))
+                break;
+            /* FALLTHROUGH */
+        case 8: /* steady st cursor */
+            g.u = stcursor;
+            xdrawglyph(g, cx, cy);
+            break;
         }
     } else {
         XftDrawRect(xw.draw, &drawcol,
-                borderpx + cx * win.cw,
-                borderpx + cy * win.ch,
+                win.hborderpx + cx * win.cw,
+                win.vborderpx + cy * win.ch,
                 win.cw - 1, 1);
         XftDrawRect(xw.draw, &drawcol,
-                borderpx + cx * win.cw,
-                borderpx + cy * win.ch,
+                win.hborderpx + cx * win.cw,
+                win.vborderpx + cy * win.ch,
                 1, win.ch - 1);
         XftDrawRect(xw.draw, &drawcol,
-                borderpx + (cx + 1) * win.cw - 1,
-                borderpx + cy * win.ch,
+                win.hborderpx + (cx + 1) * win.cw - 1,
+                win.vborderpx + cy * win.ch,
                 1, win.ch - 1);
         XftDrawRect(xw.draw, &drawcol,
-                borderpx + cx * win.cw,
-                borderpx + (cy + 1) * win.ch - 1,
+                win.hborderpx + cx * win.cw,
+                win.vborderpx + (cy + 1) * win.ch - 1,
                 win.cw, 1);
     }
 }
@@ -1754,9 +1778,12 @@ xsetmode(int set, unsigned int flags)
 int
 xsetcursor(int cursor)
 {
-    if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */
+    if (!BETWEEN(cursor, 0, 8)) /* 7-8: st extensions */
         return 1;
     win.cursor = cursor;
+    cursorblinks = win.cursor == 0 || win.cursor == 1 ||
+                   win.cursor == 3 || win.cursor == 5 ||
+                   win.cursor == 7;
     return 0;
 }
 
@@ -2000,6 +2027,10 @@ run(void)
         if (FD_ISSET(ttyfd, &rfd) || xev) {
             if (!drawing) {
                 trigger = now;
+                if (IS_SET(MODE_BLINK)) {
+                    win.mode ^= MODE_BLINK;
+                }
+                lastblink = now;
                 drawing = 1;
             }
             timeout = (maxlatency - TIMEDIFF(now, trigger)) \
@@ -2010,7 +2041,7 @@ run(void)
 
         /* idle detected or maxlatency exhausted -> draw */
         timeout = -1;
-        if (blinktimeout && tattrset(ATTR_BLINK)) {
+        if (blinktimeout && (cursorblinks || tattrset(ATTR_BLINK))) {
             timeout = blinktimeout - TIMEDIFF(now, lastblink);
             if (timeout <= 0) {
                 if (-timeout > blinktimeout) /* start visible */
@@ -2041,12 +2072,53 @@ usage(void)
         " [stty_args ...]\n", argv0, argv0);
 }
 
+void
+nextscheme(const Arg *arg)
+{
+    colorscheme += arg->i;
+    if (colorscheme >= (int)LEN(schemes))
+        colorscheme = 0;
+    else if (colorscheme < 0)
+        colorscheme = LEN(schemes) - 1;
+    updatescheme();
+}
+
+void
+selectscheme(const Arg *arg)
+{
+    if (BETWEEN(arg->i, 0, LEN(schemes)-1)) {
+        colorscheme = arg->i;
+        updatescheme();
+    }
+}
+
+void
+updatescheme(void)
+{
+    int oldbg, oldfg;
+
+    oldbg = defaultbg;
+    oldfg = defaultfg;
+    colorname = schemes[colorscheme].colors;
+    defaultbg = schemes[colorscheme].bg;
+    defaultfg = schemes[colorscheme].fg;
+    defaultcs = schemes[colorscheme].cs;
+    defaultrcs = schemes[colorscheme].rcs;
+    xloadcols();
+    if (defaultbg != oldbg)
+        tupdatebgcolor(oldbg, defaultbg);
+    if (defaultfg != oldfg)
+        tupdatefgcolor(oldfg, defaultfg);
+    cresize(win.w, win.h);
+    redraw();
+}
+
 int
 main(int argc, char *argv[])
 {
     xw.l = xw.t = 0;
     xw.isfixed = False;
-    xsetcursor(cursorshape);
+    xsetcursor(cursorstyle);
 
     ARGBEGIN {
     case 'a':
@@ -2096,6 +2168,12 @@ main(int argc, char *argv[])
     } ARGEND;
 
 run:
+    colorname = schemes[colorscheme].colors;
+    defaultbg = schemes[colorscheme].bg;
+    defaultfg = schemes[colorscheme].fg;
+    defaultcs = schemes[colorscheme].cs;
+    defaultrcs = schemes[colorscheme].rcs;
+
     if (argc > 0) /* eat all remaining arguments */
         opt_cmd = argv;