Reflow patch - st - 💻 personal variant of st
 (HTM) git clone https://git.drkhsh.at/st.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 1e2641568971e649bdc7ca02dec8da89af9b15b4
 (DIR) parent 64f3ef73fc74f46994c0f540918970f701ec4888
 (HTM) Author: drkhsh <me@drkhsh.at>
       Date:   Tue,  1 Jul 2025 20:14:09 +0200
       
       Reflow patch
       
       Allows st to be resized without cutting off text when the terminal
       window is made larger again. Text wraps when the terminal window is made
       smaller.
       
       Comes with scrollback.
       
       https://github.com/bakkeby/st-flexipatch
       
       Diffstat:
         M config.def.h                        |       6 ++++--
         A patch/reflow.c                      |     759 +++++++++++++++++++++++++++++++
         A patch/reflow.h                      |      44 +++++++++++++++++++++++++++++++
         M st.c                                |     507 +++++--------------------------
         M st.h                                |       6 ++++++
         M x.c                                 |       2 ++
       
       6 files changed, 891 insertions(+), 433 deletions(-)
       ---
 (DIR) diff --git a/config.def.h b/config.def.h
       @@ -177,9 +177,11 @@ static MouseShortcut mshortcuts[] = {
                /* mask                 button   function        argument       release  screen */
                { 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"} },
       +        { XK_ANY_MOD,           Button4, kscrollup,      {.i = 1},      0, S_PRI },
       +        { XK_ANY_MOD,           Button5, kscrolldown,    {.i = 1},      0, S_PRI },
       +        { XK_ANY_MOD,           Button4, ttysend,        {.s = "\031"}, 0, S_ALT },
       +        { XK_ANY_MOD,           Button5, ttysend,        {.s = "\005"}, 0, S_ALT },
        };
        
        /* Internal keyboard shortcuts. */
 (DIR) diff --git a/patch/reflow.c b/patch/reflow.c
       @@ -0,0 +1,759 @@
       +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;
       +                kscrolldown(&((Arg){ .i = term.scr }));
       +                tswapscreen();
       +                tresizealt(col, row);
       +        }
       +        if (clear) {
       +                tclearregion(0, 0, term.col-1, term.row-1, 1);
       +        }
       +}
       +
       +void
       +selmove(int n)
       +{
       +        sel.ob.y += n, sel.nb.y += n;
       +        sel.oe.y += n, sel.ne.y += n;
       +}
       +
       +void
       +tclearglyph(Glyph *gp, int usecurattr)
       +{
       +        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 = ' ';
       +}
       +
       +void
       +treflow(int col, int row)
       +{
       +        int i, j;
       +        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 buflen, nlines;
       +        Line *buf, bufline, line;
       +
       +        /* y coordinate of cursor line end */
       +        for (oce = term.c.y; oce < term.row - 1 &&
       +                             tiswrapped(term.line[oce]); oce++);
       +
       +        nlines = HISTSIZE + row;
       +        buf = xmalloc(nlines * sizeof(Line));
       +        do {
       +                if (!nx && ++ny < nlines)
       +                        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 */
       +                bufline = buf[ny % nlines];
       +                if (col - nx > len - ox) {
       +                        memcpy(&bufline[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(&bufline[j], 0);
       +                                nx = 0;
       +                        } else if (nx > 0) {
       +                                bufline[nx - 1].mode &= ~ATTR_WRAP;
       +                        }
       +                        ox = 0, oy++;
       +                } else if (col - nx == len - ox) {
       +                        memcpy(&bufline[nx], &line[ox], (col-nx) * sizeof(Glyph));
       +                        ox = 0, oy++, nx = 0;
       +                } else/* if (col - nx < len - ox) */ {
       +                        memcpy(&bufline[nx], &line[ox], (col-nx) * sizeof(Glyph));
       +                        if (bufline[col - 1].mode & ATTR_WIDE) {
       +                                bufline[col - 2].mode |= ATTR_WRAP;
       +                                tclearglyph(&bufline[col - 1], 0);
       +                                ox--;
       +                        } else {
       +                                bufline[col - 1].mode |= ATTR_WRAP;
       +                        }
       +                        ox += col - nx;
       +                        nx = 0;
       +                }
       +        } while (oy <= oce);
       +        if (nx)
       +                for (j = nx; j < col; j++)
       +                        tclearglyph(&bufline[j], 0);
       +
       +        /* free extra lines */
       +        for (i = row; i < term.row; i++)
       +                free(term.line[i]);
       +        /* resize to new height */
       +        term.line = xrealloc(term.line, row * sizeof(Line));
       +
       +        buflen = MIN(ny + 1, nlines);
       +        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-- % nlines]);
       +                        buflen--;
       +                        term.c.y++;
       +                }
       +        }
       +        /* allocate new rows */
       +        for (i = row - 1; i > nce; i--) {
       +                if (i >= term.row)
       +                        term.line[i] = xmalloc(col * sizeof(Glyph));
       +                else
       +                        term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
       +                for (j = 0; j < col; j++)
       +                        tclearglyph(&term.line[i][j], 0);
       +        }
       +        /* fill visible area */
       +        for (/*i = nce */; i >= term.row; i--, ny--, buflen--)
       +                term.line[i] = buf[ny % nlines];
       +        for (/*i = term.row - 1 */; i >= 0; i--, ny--, buflen--) {
       +                free(term.line[i]);
       +                term.line[i] = buf[ny % nlines];
       +        }
       +        /* fill lines in history buffer and update term.histf */
       +        for (/*i = -1 */; buflen > 0 && i >= -HISTSIZE; i--, ny--, buflen--) {
       +                j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE;
       +                free(term.hist[j]);
       +                term.hist[j] = buf[ny % nlines];
       +        }
       +        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));
       +        }
       +
       +        for (; buflen > 0; ny--, buflen--)
       +                free(buf[ny % nlines]);
       +        free(buf);
       +}
       +
       +void
       +rscrolldown(int n)
       +{
       +        int i;
       +        Line temp;
       +
       +        /* can never be true as of now
       +        if (IS_SET(MODE_ALTSCREEN))
       +                return; */
       +
       +        if ((n = MIN(n, term.histf)) <= 0)
       +                return;
       +
       +        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
       +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;
       +                }
       +                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);
       +                }
       +                /* 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;
       +        }
       +        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
       +kscrolldown(const Arg* a)
       +{
       +        int n = a->i;
       +
       +        if (!term.scr || IS_SET(MODE_ALTSCREEN))
       +                return;
       +
       +        if (n < 0)
       +                n = MAX(term.row / -n, 1);
       +
       +        if (n <= term.scr) {
       +                term.scr -= n;
       +        } else {
       +                n = term.scr;
       +                term.scr = 0;
       +        }
       +
       +        if (sel.ob.x != -1 && !sel.alt)
       +                selmove(-n); /* negate change in term.scr */
       +        tfulldirt();
       +
       +}
       +
       +void
       +kscrollup(const Arg* a)
       +{
       +        int n = a->i;
       +
       +        if (!term.histf || IS_SET(MODE_ALTSCREEN))
       +                return;
       +
       +        if (n < 0)
       +                n = MAX(term.row / -n, 1);
       +
       +        if (term.scr + n <= term.histf) {
       +                term.scr += n;
       +        } 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
       +tscrollup(int top, int bot, int n, int mode)
       +{
       +
       +        int i, j, s;
       +        Line temp;
       +        int alt = IS_SET(MODE_ALTSCREEN);
       +        int savehist = !alt && top == 0 && mode != SCROLL_NOSAVEHIST;
       +        int scr = alt ? 0 : term.scr;
       +
       +        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 + scr, bot + scr);
       +        }
       +
       +        for (i = top; i <= bot-n; i++) {
       +                temp = term.line[i];
       +                term.line[i] = term.line[i+n];
       +                term.line[i+n] = temp;
       +        }
       +
       +        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
       +tscrolldown(int top, int n)
       +{
       +
       +        int i, bot = term.bot;
       +        int scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr;
       +        int itop = top + scr, ibot = bot + scr;
       +        Line temp;
       +
       +        if (n <= 0)
       +                return;
       +        n = MIN(n, bot-top+1);
       +
       +        tsetdirt(top + scr, bot + scr);
       +        tclearregion(0, bot-n+1, term.col-1, bot, 1);
       +
       +        for (i = bot; i >= top+n; i--) {
       +                temp = term.line[i];
       +                term.line[i] = term.line[i-n];
       +                term.line[i-n] = temp;
       +        }
       +
       +        if (sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN))
       +                selscroll(top, bot, n);
       +}
       +
       +void
       +tresize(int col, int row)
       +{
       +        int *bp;
       +
       +        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;
       +        }
       +
       +        if (IS_SET(MODE_ALTSCREEN))
       +                tresizealt(col, row);
       +        else
       +                tresizedef(col, row);
       +}
       +
       +void
       +tclearregion(int x1, int y1, int x2, int y2, int usecurattr)
       +{
       +        int x, y;
       +
       +        /* regionselected() takes relative coordinates */
       +        if (regionselected(x1, y1+term.scr, x2, y2+term.scr))
       +                selclear();
       +
       +        for (y = y1; y <= y2; y++) {
       +                term.dirty[y] = 1;
       +                for (x = x1; x <= x2; x++)
       +                        tclearglyph(&term.line[y][x], usecurattr);
       +        }
       +}
       +
       +void
       +tnew(int col, int row)
       +{
       +        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();
       +}
       +
       +void
       +tdeletechar(int n)
       +{
       +        int src, dst, size;
       +        Line line;
       +
       +        if (n <= 0)
       +                return;
       +        dst = term.c.x;
       +        src = MIN(term.c.x + n, term.col);
       +        size = term.col - src;
       +        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 src, dst, size;
       +        Line line;
       +
       +        if (n <= 0)
       +                return;
       +        dst = MIN(term.c.x + n, term.col);
       +        src = term.c.x;
       +        size = term.col - dst;
       +
       +        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);
       +}
       +
       +int
       +tlinelen(Line line)
       +{
       +        int i = term.col - 1;
       +
       +        /* We are using a different algorithm on the alt screen because an
       +         * application might use spaces to clear the screen and in that case it is
       +         * impossible to find the end of the line when every cell has the ATTR_SET
       +         * attribute. The second algorithm is more accurate on the main screen and
       +         * and we can use it there. */
       +        if (IS_SET(MODE_ALTSCREEN))
       +                for (; i >= 0 && !(line[i].mode & ATTR_WRAP) && line[i].u == ' '; i--);
       +        else
       +                for (; i >= 0 && !(line[i].mode & (ATTR_SET | ATTR_WRAP)); i--);
       +
       +        return i + 1;
       +}
       +
       +int
       +tiswrapped(Line line)
       +{
       +        int len = tlinelen(line);
       +
       +        return len > 0 && (line[len - 1].mode & ATTR_WRAP);
       +}
       +
       +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;
       +}
       +
       +int
       +regionselected(int x1, int y1, int x2, int y2)
       +{
       +        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;
       +
       +        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);
       +}
       +
       +int
       +selected(int x, int y)
       +{
       +        return regionselected(x, y, x, y);
       +}
       +
       +void
       +selsnap(int *x, int *y, int direction)
       +{
       +        int newx, newy;
       +        int rtop = 0, rbot = term.row - 1;
       +        int delim, prevdelim, maxlen;
       +        const Glyph *gp, *prevgp;
       +
       +        if (!IS_SET(MODE_ALTSCREEN))
       +                rtop += -term.histf + term.scr, rbot += term.scr;
       +
       +        switch (sel.snap) {
       +        case SNAP_WORD:
       +                /*
       +                 * Snap around if the word wraps around at the end or
       +                 * beginning of a line.
       +                 */
       +                maxlen = (TLINE(*y)[term.col-2].mode & ATTR_WRAP) ? term.col-1 : term.col;
       +                LIMIT(*x, 0, maxlen - 1);
       +                prevgp = &TLINE(*y)[*x];
       +                prevdelim = ISDELIM(prevgp->u);
       +                for (;;) {
       +                        newx = *x + direction;
       +                        newy = *y;
       +                        if (!BETWEEN(newx, 0, maxlen - 1)) {
       +                                newy += direction;
       +                                if (!BETWEEN(newy, rtop, rbot))
       +                                        break;
       +
       +                                if (!tiswrapped(TLINE(direction > 0 ? *y : newy)))
       +                                        break;
       +
       +                                maxlen = (TLINE(newy)[term.col-2].mode & ATTR_WRAP) ? term.col-1 : term.col;
       +                                newx = direction > 0 ? 0 : maxlen - 1;
       +                        }
       +
       +                        gp = &TLINE(newy)[newx];
       +                        delim = ISDELIM(gp->u);
       +                        if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
       +                                        || (delim && gp->u != prevgp->u)))
       +                                break;
       +
       +                        *x = newx;
       +                        *y = newy;
       +                        if (!(gp->mode & ATTR_WDUMMY)) {
       +                                prevgp = gp;
       +                                prevdelim = delim;
       +                        }
       +                }
       +                break;
       +        case SNAP_LINE:
       +                /*
       +                 * Snap around if the the previous line or the current one
       +                 * has set ATTR_WRAP at its end. Then the whole next or
       +                 * previous line will be selected.
       +                 */
       +                *x = (direction < 0) ? 0 : term.col - 1;
       +                if (direction < 0) {
       +                        for (; *y > rtop; *y -= 1) {
       +                                if (!tiswrapped(TLINE(*y-1)))
       +                                        break;
       +                        }
       +                } else if (direction > 0) {
       +                        for (; *y < rbot; *y += 1) {
       +                                if (!tiswrapped(TLINE(*y)))
       +                                        break;
       +                        }
       +                }
       +                break;
       +        }
       +}
       +
       +void
       +selscroll(int top, int bot, int n)
       +{
       +        /* turn absolute coordinates into relative */
       +        top += term.scr, bot += term.scr;
       +
       +        if (BETWEEN(sel.nb.y, top, bot) != BETWEEN(sel.ne.y, top, bot)) {
       +                selclear();
       +        } else if (BETWEEN(sel.nb.y, top, bot)) {
       +                selmove(n);
       +                if (sel.nb.y < top || sel.ne.y > bot)
       +                        selclear();
       +        }
       +}
       +
       +void
       +tswapscreen(void)
       +{
       +        static Line *altline;
       +        static int altcol, altrow;
       +        Line *tmpline = term.line;
       +        int tmpcol = term.col, tmprow = term.row;
       +
       +        term.line = altline;
       +        term.col = altcol, term.row = altrow;
       +        altline = tmpline;
       +        altcol = tmpcol, altrow = tmprow;
       +        term.mode ^= MODE_ALTSCREEN;
       +
       +}
       +
       +char *
       +getsel(void)
       +{
       +        char *str, *ptr;
       +        int y, lastx, linelen;
       +        const Glyph *gp, *lgp;
       +
       +        if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
       +                return NULL;
       +
       +        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++) {
       +                Line line = TLINE(y);
       +
       +                if ((linelen = tlinelen(line)) == 0) {
       +                        *ptr++ = '\n';
       +                        continue;
       +                }
       +
       +                if (sel.type == SEL_RECTANGULAR) {
       +                        gp = &line[sel.nb.x];
       +                        lastx = sel.ne.x;
       +                } else {
       +                        gp = &line[sel.nb.y == y ? sel.nb.x : 0];
       +                        lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
       +                }
       +                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.
       +                 * The best solution seems like to produce '\n' when
       +                 * something is copied from st and convert '\n' to
       +                 * '\r', when something to be pasted is received by
       +                 * st.
       +                 * FIXME: Fix the computer world.
       +                 */
       +                if ((y < sel.ne.y || lastx >= linelen) &&
       +                    (!(lgp->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
       +                        *ptr++ = '\n';
       +        }
       +        *ptr = '\0';
       +        return str;
       +}
       +
       +void
       +tdumpline(int n)
       +{
       +        char str[(term.col + 1) * UTF_SIZ];
       +
       +        tprinter(str, tgetline(str, &term.line[n][0]));
       +}
 (DIR) diff --git a/patch/reflow.h b/patch/reflow.h
       @@ -0,0 +1,44 @@
       +#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);
       +
       +static int tiswrapped(Line line);
       +static size_t tgetline(char *, const Glyph *);
       +static inline int regionselected(int, int, int, int);
       +static void tloaddefscreen(int, int);
       +static void tloadaltscreen(int, int);
       +static void selmove(int);
       +static inline void tclearglyph(Glyph *, int);
       +static void treflow(int, int);
       +static void rscrolldown(int);
       +static void tresizedef(int, int);
       +static void tresizealt(int, int);
       +void kscrolldown(const Arg *);
       +void kscrollup(const Arg *);
       +static void tscrollup(int, int, int, int);
       +static void tclearregion(int, int, int, int, int);
       +static void tdeletechar(int);
       +static int tlinelen(Line len);
       +static char * tgetglyphs(char *buf, const Glyph *gp, const Glyph *lgp);
       +static void selscroll(int, int, int);
       +
       +typedef struct {
       +         uint b;
       +         uint mask;
       +         void (*func)(const Arg *);
       +         const Arg arg;
       +} MouseKey;
       +
       +extern MouseKey mkeys[];
 (DIR) diff --git a/st.c b/st.c
       @@ -56,6 +56,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
       @@ -148,21 +154,17 @@ 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 tcursor(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 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);
        static void tscrolldown(int, int);
        static void tsetattr(const int *, int);
        static void tsetchar(Rune, const Glyph *, int, int);
       @@ -178,7 +180,6 @@ static int32_t tdefcolor(const int *, int *, int);
        static void tdeftran(char);
        static void tstrsequence(uchar);
        static void selnormalize(void);
       -static void selscroll(int, int);
        static void selsnap(int *, int *, int);
        
        static size_t utf8decode(const char *, Rune *, size_t);
       @@ -204,6 +205,8 @@ static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
        static const Rune utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
        static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
        
       +#include "patch/reflow.h"
       +
        ssize_t
        xwrite(int fd, const char *s, size_t len)
        {
       @@ -379,20 +382,6 @@ selinit(void)
                sel.ob.x = -1;
        }
        
       -int
       -tlinelen(int y)
       -{
       -        int i = term.col;
       -
       -        if (term.line[y][i - 1].mode & ATTR_WRAP)
       -                return i;
       -
       -        while (i > 0 && term.line[y][i - 1].u == ' ')
       -                --i;
       -
       -        return i;
       -}
       -
        void
        selstart(int col, int row, int snap)
        {
       @@ -460,157 +449,14 @@ 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)
       +        if (sel.ne.x >= tlinelen(TLINE(sel.ne.y)))
                        sel.ne.x = term.col - 1;
        }
        
       -int
       -selected(int x, int y)
       -{
       -        if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
       -                        sel.alt != IS_SET(MODE_ALTSCREEN))
       -                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 BETWEEN(y, sel.nb.y, sel.ne.y)
       -            && (y != sel.nb.y || x >= sel.nb.x)
       -            && (y != sel.ne.y || x <= sel.ne.x);
       -}
       -
       -void
       -selsnap(int *x, int *y, int direction)
       -{
       -        int newx, newy, xt, yt;
       -        int delim, prevdelim;
       -        const Glyph *gp, *prevgp;
       -
       -        switch (sel.snap) {
       -        case SNAP_WORD:
       -                /*
       -                 * Snap around if the word wraps around at the end or
       -                 * beginning of a line.
       -                 */
       -                prevgp = &term.line[*y][*x];
       -                prevdelim = ISDELIM(prevgp->u);
       -                for (;;) {
       -                        newx = *x + direction;
       -                        newy = *y;
       -                        if (!BETWEEN(newx, 0, term.col - 1)) {
       -                                newy += direction;
       -                                newx = (newx + term.col) % term.col;
       -                                if (!BETWEEN(newy, 0, term.row - 1))
       -                                        break;
       -
       -                                if (direction > 0)
       -                                        yt = *y, xt = *x;
       -                                else
       -                                        yt = newy, xt = newx;
       -                                if (!(term.line[yt][xt].mode & ATTR_WRAP))
       -                                        break;
       -                        }
       -
       -                        if (newx >= tlinelen(newy))
       -                                break;
       -
       -                        gp = &term.line[newy][newx];
       -                        delim = ISDELIM(gp->u);
       -                        if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
       -                                        || (delim && gp->u != prevgp->u)))
       -                                break;
       -
       -                        *x = newx;
       -                        *y = newy;
       -                        prevgp = gp;
       -                        prevdelim = delim;
       -                }
       -                break;
       -        case SNAP_LINE:
       -                /*
       -                 * Snap around if the the previous line or the current one
       -                 * has set ATTR_WRAP at its end. Then the whole next or
       -                 * previous line will be selected.
       -                 */
       -                *x = (direction < 0) ? 0 : term.col - 1;
       -                if (direction < 0) {
       -                        for (; *y > 0; *y += direction) {
       -                                if (!(term.line[*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
       -                                                & ATTR_WRAP)) {
       -                                        break;
       -                                }
       -                        }
       -                }
       -                break;
       -        }
       -}
       -
       -char *
       -getsel(void)
       -{
       -        char *str, *ptr;
       -        int y, bufsize, lastx, linelen;
       -        const Glyph *gp, *last;
       -
       -        if (sel.ob.x == -1)
       -                return NULL;
       -
       -        bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
       -        ptr = str = xmalloc(bufsize);
       -
       -        /* append every set & selected glyph to the selection */
       -        for (y = sel.nb.y; y <= sel.ne.y; y++) {
       -                if ((linelen = tlinelen(y)) == 0) {
       -                        *ptr++ = '\n';
       -                        continue;
       -                }
       -
       -                if (sel.type == SEL_RECTANGULAR) {
       -                        gp = &term.line[y][sel.nb.x];
       -                        lastx = sel.ne.x;
       -                } else {
       -                        gp = &term.line[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)];
       -                while (last >= gp && last->u == ' ')
       -                        --last;
       -
       -                for ( ; gp <= last; ++gp) {
       -                        if (gp->mode & ATTR_WDUMMY)
       -                                continue;
       -
       -                        ptr += utf8encode(gp->u, ptr);
       -                }
       -
       -                /*
       -                 * Copy and pasting of line endings is inconsistent
       -                 * in the inconsistent terminal and GUI world.
       -                 * The best solution seems like to produce '\n' when
       -                 * something is copied from st and convert '\n' to
       -                 * '\r', when something to be pasted is received by
       -                 * st.
       -                 * FIXME: Fix the computer world.
       -                 */
       -                if ((y < sel.ne.y || lastx >= linelen) &&
       -                    (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
       -                        *ptr++ = '\n';
       -        }
       -        *ptr = 0;
       -        return str;
       -}
       -
        void
        selclear(void)
        {
       @@ -829,6 +675,7 @@ void
        ttywrite(const char *s, size_t n, int may_echo)
        {
                const char *next;
       +        kscrolldown(&((Arg){ .i = term.scr }));
        
                if (may_echo && IS_SET(MODE_ECHO))
                        twrite(s, n, 1);
       @@ -971,7 +818,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;
                                }
                        }
       @@ -981,7 +828,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
       @@ -1009,6 +857,7 @@ void
        treset(void)
        {
                uint i;
       +        int x, y;
        
                tresetcursor();
        
       @@ -1020,101 +869,28 @@ treset(void)
                term.mode = MODE_WRAP|MODE_UTF8;
                memset(term.trantbl, CS_USA, sizeof(term.trantbl));
                term.charset = 0;
       +        term.histf = 0;
       +        term.histi = 0;
       +        term.scr = 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();
                }
       -}
       -
       -void
       -tnew(int col, int row)
       -{
       -        term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
       -        tresize(col, row);
       -        treset();
       -}
       -
       -void
       -tswapscreen(void)
       -{
       -        Line *tmp = term.line;
       -
       -        term.line = term.alt;
       -        term.alt = tmp;
       -        term.mode ^= MODE_ALTSCREEN;
                tfulldirt();
        }
        
        void
       -tscrolldown(int orig, int n)
       -{
       -        int i;
       -        Line temp;
       -
       -        LIMIT(n, 0, term.bot-orig+1);
       -
       -        tsetdirt(orig, term.bot-n);
       -        tclearregion(0, term.bot-n+1, term.col-1, term.bot);
       -
       -        for (i = term.bot; i >= orig+n; i--) {
       -                temp = term.line[i];
       -                term.line[i] = term.line[i-n];
       -                term.line[i-n] = temp;
       -        }
       -
       -        selscroll(orig, n);
       -}
       -
       -void
       -tscrollup(int orig, int n)
       -{
       -        int i;
       -        Line temp;
       -
       -        LIMIT(n, 0, term.bot-orig+1);
       -
       -        tclearregion(0, orig, term.col-1, orig+n-1);
       -        tsetdirt(orig+n, term.bot);
       -
       -        for (i = orig; i <= term.bot-n; i++) {
       -                temp = term.line[i];
       -                term.line[i] = term.line[i+n];
       -                term.line[i+n] = temp;
       -        }
       -
       -        selscroll(orig, -n);
       -}
       -
       -void
       -selscroll(int orig, int n)
       -{
       -        if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
       -                return;
       -
       -        if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.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) {
       -                        selclear();
       -                } else {
       -                        selnormalize();
       -                }
       -        }
       -}
       -
       -void
        tnewline(int first_col)
        {
                int y = term.c.y;
        
                if (y == term.bot) {
       -                tscrollup(term.top, 1);
       +                tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST);
                } else {
                        y++;
                }
       @@ -1216,70 +992,6 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
        }
        
        void
       -tclearregion(int x1, int y1, int x2, int y2)
       -{
       -        int x, y, temp;
       -        Glyph *gp;
       -
       -        if (x1 > x2)
       -                temp = x1, x1 = x2, x2 = temp;
       -        if (y1 > y2)
       -                temp = y1, y1 = y2, y2 = temp;
       -
       -        LIMIT(x1, 0, term.col-1);
       -        LIMIT(x2, 0, term.col-1);
       -        LIMIT(y1, 0, term.row-1);
       -        LIMIT(y2, 0, term.row-1);
       -
       -        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 = ' ';
       -                }
       -        }
       -}
       -
       -void
       -tdeletechar(int n)
       -{
       -        int dst, src, size;
       -        Glyph *line;
       -
       -        LIMIT(n, 0, term.col - term.c.x);
       -
       -        dst = term.c.x;
       -        src = term.c.x + n;
       -        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);
       -}
       -
       -void
       -tinsertblank(int n)
       -{
       -        int dst, src, size;
       -        Glyph *line;
       -
       -        LIMIT(n, 0, term.col - term.c.x);
       -
       -        dst = term.c.x + n;
       -        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);
       -}
       -
       -void
        tinsertblankline(int n)
        {
                if (BETWEEN(term.c.y, term.top, term.bot))
       @@ -1290,7 +1002,7 @@ void
        tdeleteline(int n)
        {
                if (BETWEEN(term.c.y, term.top, term.bot))
       -                tscrollup(term.c.y, n);
       +                tscrollup(term.c.y, term.bot, n, SCROLL_NOSAVEHIST);
        }
        
        int32_t
       @@ -1534,17 +1246,14 @@ tsetmode(int priv, int set, const int *args, int narg)
                                case 1047:
                                        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 != 47, *args == 1049);
       +                                else
       +                                        tloaddefscreen(*args != 47, *args == 1049);
       +                                break;
                                case 1048:
       +                                if (!allowaltscreen)
       +                                        break;
                                        tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
                                        break;
                                case 2004: /* 2004: bracketed paste mode */
       @@ -1597,6 +1306,7 @@ csihandle(void)
        {
                char buf[40];
                int n = 0, len;
       +        int x;
                int maxcol = term.col;
        
                switch (csiescseq.mode[0]) {
       @@ -1695,20 +1405,37 @@ csihandle(void)
                case 'J': /* ED -- Clear screen */
                        switch (csiescseq.arg[0]) {
                        case 0: /* below */
       -                        tclearregion(term.c.x, term.c.y, maxcol-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, maxcol-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 > 0)
       -                                tclearregion(0, 0, maxcol-1, term.c.y-1);
       -                        tclearregion(0, term.c.y, term.c.x, term.c.y);
       +                                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: /* screen */
       -
       -                        tclearregion(0, 0, maxcol-1, term.row-1);
       +                        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;
                                break;
                        case 3: /* scrollback */
       +                        if (IS_SET(MODE_ALTSCREEN))
       +                                break;
       +                        kscrolldown(&((Arg){ .i = term.scr }));
       +                        term.scr = 0;
       +                        term.histi = 0;
       +                        term.histf = 0;
                                break;
                        default:
                                goto unknown;
       @@ -1717,13 +1444,13 @@ csihandle(void)
                case 'K': /* EL -- Clear line */
                        switch (csiescseq.arg[0]) {
                        case 0: /* right */
       -                        tclearregion(term.c.x, term.c.y, maxcol-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, maxcol-1, term.c.y);
       +                        tclearregion(0, term.c.y, term.col-1, term.c.y, 1);
                                break;
                        }
                        break;
       @@ -1732,7 +1459,8 @@ csihandle(void)
                                goto unknown;
                        }
                        DEFAULT(csiescseq.arg[0], 1);
       -                tscrollup(term.top, csiescseq.arg[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);
       @@ -1750,9 +1478,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);
       @@ -2092,21 +1822,6 @@ 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);
       -}
       -
       -void
        tdump(void)
        {
                int i;
       @@ -2325,7 +2040,7 @@ eschandle(uchar ascii)
                        return 0;
                case 'D': /* IND -- Linefeed */
                        if (term.c.y == term.bot) {
       -                        tscrollup(term.top, 1);
       +                        tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST);
                        } else {
                                tmoveto(term.c.x, term.c.y+1);
                        }
       @@ -2484,7 +2199,9 @@ check_control_code:
                         */
                        return;
                }
       -        if (selected(term.c.x, term.c.y))
       +
       +        /* selected() takes relative coordinates */
       +        if (selected(term.c.x, term.c.y + term.scr))
                        selclear();
        
                gp = &term.line[term.c.y][term.c.x];
       @@ -2524,6 +2241,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;
                }
        }
       @@ -2561,82 +2279,6 @@ twrite(const char *buf, int buflen, int show_ctrl)
        }
        
        void
       -tresize(int col, int row)
       -{
       -        int i, j;
       -        int minrow = MIN(row, term.row);
       -        int mincol = MIN(col, term.col);
       -        int *bp;
       -
       -        if (col < 1 || row < 1) {
       -                fprintf(stderr,
       -                        "tresize: error resizing to %dx%d\n", col, row);
       -                return;
       -        }
       -
       -        /* scroll both screens independently */
       -        if (row < term.row) {
       -                tcursor(CURSOR_SAVE);
       -                tsetscroll(0, term.row - 1);
       -                for (i = 0; i < 2; i++) {
       -                        if (term.c.y >= row) {
       -                                tscrollup(0, term.c.y - row + 1);
       -                        }
       -                        for (j = row; j < term.row; j++)
       -                                free(term.line[j]);
       -                        tswapscreen();
       -                        tcursor(CURSOR_LOAD);
       -                }
       -        }
       -
       -        /* 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));
       -        }
       -        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);
       -        /* Clearing both screens (it makes dirty all lines) */
       -        for (i = 0; i < 2; i++) {
       -                tmoveto(term.c.x, term.c.y);  /* make use of the LIMIT in tmoveto */
       -                tcursor(CURSOR_SAVE);
       -                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);
       -        }
       -
       -}
       -
       -void
        resettitle(void)
        {
                xsettitle(NULL);
       @@ -2652,10 +2294,12 @@ 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);
                }
        }
        
       +#include "patch/reflow.c"
       +
        void
        draw(void)
        {
       @@ -2673,6 +2317,7 @@ 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]);
                term.ocx = cx;
 (DIR) diff --git a/st.h b/st.h
       @@ -26,6 +26,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,
       @@ -101,6 +102,11 @@ typedef struct {
                int col;      /* nb col */
                Line *line;   /* screen */
                Line *alt;    /* alternate screen */
       +        Line hist[HISTSIZE]; /* history buffer */
       +        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 */
 (DIR) diff --git a/x.c b/x.c
       @@ -35,6 +35,8 @@ static void zoomabs(const Arg *);
        static void zoomreset(const Arg *);
        static void ttysend(const Arg *);
        
       +#include "patch/reflow.h"
       +
        /* config.h for applying patches and the configuration. */
        #include "config.h"