st

fork of suckless's simple terminal
Index Commits Files Refs README LICENSE
patches/st-scrollback-reflow-0.8.5.diff (37784B)
   1 diff --git a/st.c b/st.c
   2 index 91e7077..a76d983 100644
   3 --- a/st.c
   4 +++ b/st.c
   5 @@ -36,6 +36,7 @@
   6  #define STR_BUF_SIZ   ESC_BUF_SIZ
   7  #define STR_ARG_SIZ   ESC_ARG_SIZ
   8  #define HISTSIZE      2000
   9 +#define RESIZEBUFFER  1000
  10  
  11  /* macros */
  12  #define IS_SET(flag)        ((term.mode & (flag)) != 0)
  13 @@ -43,9 +44,22 @@
  14  #define ISCONTROLC1(c)        (BETWEEN(c, 0x80, 0x9f))
  15  #define ISCONTROL(c)        (ISCONTROLC0(c) || ISCONTROLC1(c))
  16  #define ISDELIM(u)        (u && wcschr(worddelimiters, u))
  17 -#define TLINE(y)        ((y) < term.scr ? term.hist[((y) + term.histi - \
  18 -                term.scr + HISTSIZE + 1) % HISTSIZE] : \
  19 -                term.line[(y) - term.scr])
  20 +
  21 +#define TLINE(y) ( \
  22 +    (y) < term.scr ? term.hist[(term.histi + (y) - term.scr + 1 + HISTSIZE) % HISTSIZE] \
  23 +                   : term.line[(y) - term.scr] \
  24 +)
  25 +
  26 +#define TLINEABS(y) ( \
  27 +    (y) < 0 ? term.hist[(term.histi + (y) + 1 + HISTSIZE) % HISTSIZE] : term.line[(y)] \
  28 +)
  29 +
  30 +#define UPDATEWRAPNEXT(alt, col) do { \
  31 +    if ((term.c.state & CURSOR_WRAPNEXT) && term.c.x + term.wrapcwidth[alt] < col) { \
  32 +        term.c.x += term.wrapcwidth[alt]; \
  33 +        term.c.state &= ~CURSOR_WRAPNEXT; \
  34 +    } \
  35 +} while (0);
  36  
  37  enum term_mode {
  38      MODE_WRAP        = 1 << 0,
  39 @@ -57,6 +71,12 @@ enum term_mode {
  40      MODE_UTF8        = 1 << 6,
  41  };
  42  
  43 +enum scroll_mode {
  44 +    SCROLL_RESIZE = -1,
  45 +    SCROLL_NOSAVEHIST = 0,
  46 +    SCROLL_SAVEHIST = 1
  47 +};
  48 +
  49  enum cursor_movement {
  50      CURSOR_SAVE,
  51      CURSOR_LOAD
  52 @@ -118,10 +138,11 @@ typedef struct {
  53      int row;      /* nb row */
  54      int col;      /* nb col */
  55      Line *line;   /* screen */
  56 -    Line *alt;    /* alternate screen */
  57      Line hist[HISTSIZE]; /* history buffer */
  58 -    int histi;    /* history index */
  59 -    int scr;      /* scroll back */
  60 +    int histi;           /* history index */
  61 +    int histf;           /* nb history available */
  62 +    int scr;             /* scroll back */
  63 +    int wrapcwidth[2];   /* used in updating WRAPNEXT when resizing */
  64      int *dirty;   /* dirtyness of lines */
  65      TCursor c;    /* cursor */
  66      int ocx;      /* old cursor col */
  67 @@ -179,26 +200,37 @@ static void tprinter(char *, size_t);
  68  static void tdumpsel(void);
  69  static void tdumpline(int);
  70  static void tdump(void);
  71 -static void tclearregion(int, int, int, int);
  72 +static void tclearregion(int, int, int, int, int);
  73  static void tcursor(int);
  74 +static void tclearglyph(Glyph *, int);
  75 +static void tresetcursor(void);
  76  static void tdeletechar(int);
  77  static void tdeleteline(int);
  78  static void tinsertblank(int);
  79  static void tinsertblankline(int);
  80 -static int tlinelen(int);
  81 +static int tlinelen(Line len);
  82 +static int tiswrapped(Line line);
  83 +static char *tgetglyphs(char *, const Glyph *, const Glyph *);
  84 +static size_t tgetline(char *, const Glyph *);
  85  static void tmoveto(int, int);
  86  static void tmoveato(int, int);
  87  static void tnewline(int);
  88  static void tputtab(int);
  89  static void tputc(Rune);
  90  static void treset(void);
  91 -static void tscrollup(int, int, int);
  92 -static void tscrolldown(int, int, int);
  93 +static void tscrollup(int, int, int, int);
  94 +static void tscrolldown(int, int);
  95 +static void treflow(int, int);
  96 +static void rscrolldown(int);
  97 +static void tresizedef(int, int);
  98 +static void tresizealt(int, int);
  99  static void tsetattr(const int *, int);
 100  static void tsetchar(Rune, const Glyph *, int, int);
 101  static void tsetdirt(int, int);
 102  static void tsetscroll(int, int);
 103  static void tswapscreen(void);
 104 +static void tloaddefscreen(int, int);
 105 +static void tloadaltscreen(int, int);
 106  static void tsetmode(int, int, const int *, int);
 107  static int twrite(const char *, int, int);
 108  static void tfulldirt(void);
 109 @@ -212,7 +244,10 @@ static void tstrsequence(uchar);
 110  static void drawregion(int, int, int, int);
 111  
 112  static void selnormalize(void);
 113 -static void selscroll(int, int);
 114 +static void selscroll(int, int, int);
 115 +static void selmove(int);
 116 +static void selremove(void);
 117 +static int regionselected(int, int, int, int);
 118  static void selsnap(int *, int *, int);
 119  
 120  static size_t utf8decode(const char *, Rune *, size_t);
 121 @@ -412,17 +447,46 @@ selinit(void)
 122  }
 123  
 124  int
 125 -tlinelen(int y)
 126 +tlinelen(Line line)
 127  {
 128 -    int i = term.col;
 129 +    int i = term.col - 1;
 130 +
 131 +    for (; i >= 0 && !(line[i].mode & (ATTR_SET | ATTR_WRAP)); i--);
 132 +    return i + 1;
 133 +}
 134  
 135 -    if (TLINE(y)[i - 1].mode & ATTR_WRAP)
 136 -        return i;
 137 +int
 138 +tiswrapped(Line line)
 139 +{
 140 +    int len = tlinelen(line);
 141  
 142 -    while (i > 0 && TLINE(y)[i - 1].u == ' ')
 143 -        --i;
 144 +    return len > 0 && (line[len - 1].mode & ATTR_WRAP);
 145 +}
 146  
 147 -    return i;
 148 +char *
 149 +tgetglyphs(char *buf, const Glyph *gp, const Glyph *lgp)
 150 +{
 151 +    while (gp <= lgp)
 152 +        if (gp->mode & ATTR_WDUMMY) {
 153 +            gp++;
 154 +        } else {
 155 +            buf += utf8encode((gp++)->u, buf);
 156 +        }
 157 +    return buf;
 158 +}
 159 +
 160 +size_t
 161 +tgetline(char *buf, const Glyph *fgp)
 162 +{
 163 +    char *ptr;
 164 +    const Glyph *lgp = &fgp[term.col - 1];
 165 +
 166 +    while (lgp > fgp && !(lgp->mode & (ATTR_SET | ATTR_WRAP)))
 167 +        lgp--;
 168 +    ptr = tgetglyphs(buf, fgp, lgp);
 169 +    if (!(lgp->mode & ATTR_WRAP))
 170 +        *(ptr++) = '\n';
 171 +    return ptr - buf;
 172  }
 173  
 174  void
 175 @@ -462,10 +526,11 @@ selextend(int col, int row, int type, int done)
 176  
 177      sel.oe.x = col;
 178      sel.oe.y = row;
 179 -    selnormalize();
 180      sel.type = type;
 181 +    selnormalize();
 182  
 183 -    if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
 184 +    if (oldey != sel.oe.y || oldex != sel.oe.x ||
 185 +        oldtype != sel.type || sel.mode == SEL_EMPTY)
 186          tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
 187  
 188      sel.mode = done ? SEL_IDLE : SEL_READY;
 189 @@ -492,36 +557,43 @@ selnormalize(void)
 190      /* expand selection over line breaks */
 191      if (sel.type == SEL_RECTANGULAR)
 192          return;
 193 -    i = tlinelen(sel.nb.y);
 194 -    if (i < sel.nb.x)
 195 +
 196 +  i = tlinelen(TLINE(sel.nb.y));
 197 +    if (sel.nb.x > i)
 198          sel.nb.x = i;
 199 -    if (tlinelen(sel.ne.y) <= sel.ne.x)
 200 -        sel.ne.x = term.col - 1;
 201 +  if (sel.ne.x >= tlinelen(TLINE(sel.ne.y)))
 202 +    sel.ne.x = term.col - 1;
 203  }
 204  
 205  int
 206 -selected(int x, int y)
 207 +regionselected(int x1, int y1, int x2, int y2)
 208  {
 209 -    if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
 210 -            sel.alt != IS_SET(MODE_ALTSCREEN))
 211 +    if (sel.ob.x == -1 || sel.mode == SEL_EMPTY ||
 212 +        sel.alt != IS_SET(MODE_ALTSCREEN) || sel.nb.y > y2 || sel.ne.y < y1)
 213          return 0;
 214  
 215 -    if (sel.type == SEL_RECTANGULAR)
 216 -        return BETWEEN(y, sel.nb.y, sel.ne.y)
 217 -            && BETWEEN(x, sel.nb.x, sel.ne.x);
 218 +    return (sel.type == SEL_RECTANGULAR) ? sel.nb.x <= x2 && sel.ne.x >= x1
 219 +        : (sel.nb.y != y2 || sel.nb.x <= x2) &&
 220 +          (sel.ne.y != y1 || sel.ne.x >= x1);
 221 +}
 222  
 223 -    return BETWEEN(y, sel.nb.y, sel.ne.y)
 224 -        && (y != sel.nb.y || x >= sel.nb.x)
 225 -        && (y != sel.ne.y || x <= sel.ne.x);
 226 +int
 227 +selected(int x, int y)
 228 +{
 229 +    return regionselected(x, y, x, y);
 230  }
 231  
 232  void
 233  selsnap(int *x, int *y, int direction)
 234  {
 235      int newx, newy, xt, yt;
 236 +    int rtop = 0, rbot = term.row - 1;
 237      int delim, prevdelim;
 238      const Glyph *gp, *prevgp;
 239  
 240 +    if (!IS_SET(MODE_ALTSCREEN))
 241 +        rtop += -term.histf + term.scr, rbot += term.scr;
 242 +
 243      switch (sel.snap) {
 244      case SNAP_WORD:
 245          /*
 246 @@ -536,7 +608,7 @@ selsnap(int *x, int *y, int direction)
 247              if (!BETWEEN(newx, 0, term.col - 1)) {
 248                  newy += direction;
 249                  newx = (newx + term.col) % term.col;
 250 -                if (!BETWEEN(newy, 0, term.row - 1))
 251 +                if (!BETWEEN(newy, rtop, rbot))
 252                      break;
 253  
 254                  if (direction > 0)
 255 @@ -547,13 +619,13 @@ selsnap(int *x, int *y, int direction)
 256                      break;
 257              }
 258  
 259 -            if (newx >= tlinelen(newy))
 260 +            if (newx >= tlinelen(TLINE(newy)))
 261                  break;
 262  
 263              gp = &TLINE(newy)[newx];
 264              delim = ISDELIM(gp->u);
 265 -            if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
 266 -                    || (delim && gp->u != prevgp->u)))
 267 +            if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim ||
 268 +                (delim && !(gp->u == ' ' && prevgp->u == ' '))))
 269                  break;
 270  
 271              *x = newx;
 272 @@ -570,18 +642,14 @@ selsnap(int *x, int *y, int direction)
 273           */
 274          *x = (direction < 0) ? 0 : term.col - 1;
 275          if (direction < 0) {
 276 -            for (; *y > 0; *y += direction) {
 277 -                if (!(TLINE(*y-1)[term.col-1].mode
 278 -                        & ATTR_WRAP)) {
 279 +            for (; *y > rtop; *y -= 1) {
 280 +                if (!tiswrapped(TLINE(*y-1)))
 281                      break;
 282 -                }
 283              }
 284          } else if (direction > 0) {
 285 -            for (; *y < term.row-1; *y += direction) {
 286 -                if (!(TLINE(*y)[term.col-1].mode
 287 -                        & ATTR_WRAP)) {
 288 +            for (; *y < rbot; *y += 1) {
 289 +                if (!tiswrapped(TLINE(*y)))
 290                      break;
 291 -                }
 292              }
 293          }
 294          break;
 295 @@ -592,40 +660,34 @@ char *
 296  getsel(void)
 297  {
 298      char *str, *ptr;
 299 -    int y, bufsize, lastx, linelen;
 300 -    const Glyph *gp, *last;
 301 +    int y, lastx, linelen;
 302 +    const Glyph *gp, *lgp;
 303  
 304 -    if (sel.ob.x == -1)
 305 +    if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
 306          return NULL;
 307  
 308 -    bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
 309 -    ptr = str = xmalloc(bufsize);
 310 +    str = xmalloc((term.col + 1) * (sel.ne.y - sel.nb.y + 1) * UTF_SIZ);
 311 +    ptr = str;
 312  
 313      /* append every set & selected glyph to the selection */
 314      for (y = sel.nb.y; y <= sel.ne.y; y++) {
 315 -        if ((linelen = tlinelen(y)) == 0) {
 316 +        Line line = TLINE(y);
 317 +
 318 +        if ((linelen = tlinelen(line)) == 0) {
 319              *ptr++ = '\n';
 320              continue;
 321          }
 322  
 323          if (sel.type == SEL_RECTANGULAR) {
 324 -            gp = &TLINE(y)[sel.nb.x];
 325 +            gp = &line[sel.nb.x];
 326              lastx = sel.ne.x;
 327          } else {
 328 -            gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
 329 +            gp = &line[sel.nb.y == y ? sel.nb.x : 0];
 330              lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
 331          }
 332 -        last = &TLINE(y)[MIN(lastx, linelen-1)];
 333 -        while (last >= gp && last->u == ' ')
 334 -            --last;
 335 -
 336 -        for ( ; gp <= last; ++gp) {
 337 -            if (gp->mode & ATTR_WDUMMY)
 338 -                continue;
 339 -
 340 -            ptr += utf8encode(gp->u, ptr);
 341 -        }
 342 +        lgp = &line[MIN(lastx, linelen-1)];
 343  
 344 +        ptr = tgetglyphs(ptr, gp, lgp);
 345          /*
 346           * Copy and pasting of line endings is inconsistent
 347           * in the inconsistent terminal and GUI world.
 348 @@ -636,10 +698,10 @@ getsel(void)
 349           * FIXME: Fix the computer world.
 350           */
 351          if ((y < sel.ne.y || lastx >= linelen) &&
 352 -            (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
 353 +            (!(lgp->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
 354              *ptr++ = '\n';
 355      }
 356 -    *ptr = 0;
 357 +    *ptr = '\0';
 358      return str;
 359  }
 360  
 361 @@ -648,9 +710,15 @@ selclear(void)
 362  {
 363      if (sel.ob.x == -1)
 364          return;
 365 +    selremove();
 366 +    tsetdirt(sel.nb.y, sel.ne.y);
 367 +}
 368 +
 369 +void
 370 +selremove(void)
 371 +{
 372      sel.mode = SEL_IDLE;
 373      sel.ob.x = -1;
 374 -    tsetdirt(sel.nb.y, sel.ne.y);
 375  }
 376  
 377  void
 378 @@ -851,10 +919,8 @@ void
 379  ttywrite(const char *s, size_t n, int may_echo)
 380  {
 381      const char *next;
 382 -    Arg arg = (Arg) { .i = term.scr };
 383 -
 384 -    kscrolldown(&arg);
 385  
 386 +    kscrolldown(&((Arg){ .i = term.scr }));
 387      if (may_echo && IS_SET(MODE_ECHO))
 388          twrite(s, n, 1);
 389  
 390 @@ -990,7 +1056,7 @@ tsetdirtattr(int attr)
 391      for (i = 0; i < term.row-1; i++) {
 392          for (j = 0; j < term.col-1; j++) {
 393              if (term.line[i][j].mode & attr) {
 394 -                tsetdirt(i, i);
 395 +                term.dirty[i] = 1;
 396                  break;
 397              }
 398          }
 399 @@ -1000,7 +1066,8 @@ tsetdirtattr(int attr)
 400  void
 401  tfulldirt(void)
 402  {
 403 -    tsetdirt(0, term.row-1);
 404 +  for (int i = 0; i < term.row; i++)
 405 +        term.dirty[i] = 1;
 406  }
 407  
 408  void
 409 @@ -1017,51 +1084,116 @@ tcursor(int mode)
 410      }
 411  }
 412  
 413 +void
 414 +tresetcursor(void)
 415 +{
 416 +    term.c = (TCursor){ { .mode = ATTR_NULL, .fg = defaultfg, .bg = defaultbg },
 417 +                        .x = 0, .y = 0, .state = CURSOR_DEFAULT };
 418 +}
 419 +
 420  void
 421  treset(void)
 422  {
 423      uint i;
 424 +  int x, y;
 425  
 426 -    term.c = (TCursor){{
 427 -        .mode = ATTR_NULL,
 428 -        .fg = defaultfg,
 429 -        .bg = defaultbg
 430 -    }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
 431 +    tresetcursor();
 432  
 433      memset(term.tabs, 0, term.col * sizeof(*term.tabs));
 434      for (i = tabspaces; i < term.col; i += tabspaces)
 435          term.tabs[i] = 1;
 436      term.top = 0;
 437 +    term.histf = 0;
 438 +    term.scr = 0;
 439      term.bot = term.row - 1;
 440      term.mode = MODE_WRAP|MODE_UTF8;
 441      memset(term.trantbl, CS_USA, sizeof(term.trantbl));
 442      term.charset = 0;
 443  
 444 +  selremove();
 445      for (i = 0; i < 2; i++) {
 446 -        tmoveto(0, 0);
 447 -        tcursor(CURSOR_SAVE);
 448 -        tclearregion(0, 0, term.col-1, term.row-1);
 449 +      tcursor(CURSOR_SAVE); /* reset saved cursor */
 450 +        for (y = 0; y < term.row; y++)
 451 +            for (x = 0; x < term.col; x++)
 452 +                tclearglyph(&term.line[y][x], 0);
 453          tswapscreen();
 454      }
 455 +  tfulldirt();
 456  }
 457  
 458  void
 459  tnew(int col, int row)
 460  {
 461 -    term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
 462 -    tresize(col, row);
 463 -    treset();
 464 +    int i, j;
 465 +
 466 +    for (i = 0; i < 2; i++) {
 467 +        term.line = xmalloc(row * sizeof(Line));
 468 +        for (j = 0; j < row; j++)
 469 +            term.line[j] = xmalloc(col * sizeof(Glyph));
 470 +        term.col = col, term.row = row;
 471 +        tswapscreen();
 472 +    }
 473 +    term.dirty = xmalloc(row * sizeof(*term.dirty));
 474 +    term.tabs = xmalloc(col * sizeof(*term.tabs));
 475 +    for (i = 0; i < HISTSIZE; i++)
 476 +        term.hist[i] = xmalloc(col * sizeof(Glyph));
 477 +  treset();
 478  }
 479  
 480 +/* handle it with care */
 481  void
 482  tswapscreen(void)
 483  {
 484 -    Line *tmp = term.line;
 485 +    static Line *altline;
 486 +    static int altcol, altrow;
 487 +    Line *tmpline = term.line;
 488 +    int tmpcol = term.col, tmprow = term.row;
 489  
 490 -    term.line = term.alt;
 491 -    term.alt = tmp;
 492 +    term.line = altline;
 493 +    term.col = altcol, term.row = altrow;
 494 +    altline = tmpline;
 495 +    altcol = tmpcol, altrow = tmprow;
 496      term.mode ^= MODE_ALTSCREEN;
 497 -    tfulldirt();
 498 +}
 499 +
 500 +void
 501 +tloaddefscreen(int clear, int loadcursor)
 502 +{
 503 +    int col, row, alt = IS_SET(MODE_ALTSCREEN);
 504 +
 505 +    if (alt) {
 506 +        if (clear)
 507 +            tclearregion(0, 0, term.col-1, term.row-1, 1);
 508 +        col = term.col, row = term.row;
 509 +        tswapscreen();
 510 +    }
 511 +    if (loadcursor)
 512 +        tcursor(CURSOR_LOAD);
 513 +    if (alt)
 514 +        tresizedef(col, row);
 515 +}
 516 +
 517 +void
 518 +tloadaltscreen(int clear, int savecursor)
 519 +{
 520 +    int col, row, def = !IS_SET(MODE_ALTSCREEN);
 521 +
 522 +    if (savecursor)
 523 +        tcursor(CURSOR_SAVE);
 524 +    if (def) {
 525 +        col = term.col, row = term.row;
 526 +        tswapscreen();
 527 +        term.scr = 0;
 528 +        tresizealt(col, row);
 529 +    }
 530 +    if (clear)
 531 +        tclearregion(0, 0, term.col-1, term.row-1, 1);
 532 +}
 533 +
 534 +int
 535 +tisaltscreen(void)
 536 +{
 537 +    return IS_SET(MODE_ALTSCREEN);
 538  }
 539  
 540  void
 541 @@ -1069,17 +1201,22 @@ kscrolldown(const Arg* a)
 542  {
 543      int n = a->i;
 544  
 545 -    if (n < 0)
 546 -        n = term.row + n;
 547 +    if (!term.scr || IS_SET(MODE_ALTSCREEN))
 548 +        return;
 549  
 550 -    if (n > term.scr)
 551 -        n = term.scr;
 552 +    if (n < 0)
 553 +        n = MAX(term.row / -n, 1);
 554  
 555 -    if (term.scr > 0) {
 556 +    if (n <= term.scr) {
 557          term.scr -= n;
 558 -        selscroll(0, -n);
 559 -        tfulldirt();
 560 +    } else {
 561 +        n = term.scr;
 562 +        term.scr = 0;
 563      }
 564 +
 565 +    if (sel.ob.x != -1 && !sel.alt)
 566 +        selmove(-n); /* negate change in term.scr */
 567 +    tfulldirt();
 568  }
 569  
 570  void
 571 @@ -1087,92 +1224,118 @@ kscrollup(const Arg* a)
 572  {
 573      int n = a->i;
 574  
 575 +    if (!term.histf || IS_SET(MODE_ALTSCREEN))
 576 +        return;
 577 +
 578      if (n < 0)
 579 -        n = term.row + n;
 580 +        n = MAX(term.row / -n, 1);
 581  
 582 -    if (term.scr <= HISTSIZE-n) {
 583 +    if (term.scr + n <= term.histf) {
 584          term.scr += n;
 585 -        selscroll(0, n);
 586 -        tfulldirt();
 587 +    } else {
 588 +        n = term.histf - term.scr;
 589 +        term.scr = term.histf;
 590      }
 591 +
 592 +    if (sel.ob.x != -1 && !sel.alt)
 593 +        selmove(n); /* negate change in term.scr */
 594 +    tfulldirt();
 595  }
 596  
 597  void
 598 -tscrolldown(int orig, int n, int copyhist)
 599 +tscrolldown(int top, int n)
 600  {
 601 -    int i;
 602 +    int i, bot = term.bot;
 603      Line temp;
 604  
 605 -    LIMIT(n, 0, term.bot-orig+1);
 606 -    if (copyhist) {
 607 -        term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
 608 -        temp = term.hist[term.histi];
 609 -        term.hist[term.histi] = term.line[term.bot];
 610 -        term.line[term.bot] = temp;
 611 -    }
 612 -
 613 +    if (n <= 0)
 614 +        return;
 615 +    n = MIN(n, bot-top+1);
 616  
 617 -    tsetdirt(orig, term.bot-n);
 618 -    tclearregion(0, term.bot-n+1, term.col-1, term.bot);
 619 +    tsetdirt(top, bot-n);
 620 +    tclearregion(0, bot-n+1, term.col-1, bot, 1);
 621  
 622 -    for (i = term.bot; i >= orig+n; i--) {
 623 +    for (i = bot; i >= top+n; i--) {
 624          temp = term.line[i];
 625          term.line[i] = term.line[i-n];
 626          term.line[i-n] = temp;
 627      }
 628  
 629 -    if (term.scr == 0)
 630 -        selscroll(orig, n);
 631 +    if (sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN))
 632 +        selscroll(top, bot, n);
 633  }
 634  
 635  void
 636 -tscrollup(int orig, int n, int copyhist)
 637 +tscrollup(int top, int bot, int n, int mode)
 638  {
 639 -    int i;
 640 +    int i, j, s;
 641 +    int alt = IS_SET(MODE_ALTSCREEN);
 642 +    int savehist = !alt && top == 0 && mode != SCROLL_NOSAVEHIST;
 643      Line temp;
 644  
 645 -    LIMIT(n, 0, term.bot-orig+1);
 646 -
 647 -    if (copyhist) {
 648 -        term.histi = (term.histi + 1) % HISTSIZE;
 649 -        temp = term.hist[term.histi];
 650 -        term.hist[term.histi] = term.line[orig];
 651 -        term.line[orig] = temp;
 652 +    if (n <= 0)
 653 +        return;
 654 +    n = MIN(n, bot-top+1);
 655 +
 656 +    if (savehist) {
 657 +        for (i = 0; i < n; i++) {
 658 +            term.histi = (term.histi + 1) % HISTSIZE;
 659 +            temp = term.hist[term.histi];
 660 +            for (j = 0; j < term.col; j++)
 661 +                tclearglyph(&temp[j], 1);
 662 +            term.hist[term.histi] = term.line[i];
 663 +            term.line[i] = temp;
 664 +        }
 665 +        term.histf = MIN(term.histf + n, HISTSIZE);
 666 +        s = n;
 667 +        if (term.scr) {
 668 +            j = term.scr;
 669 +            term.scr = MIN(j + n, HISTSIZE);
 670 +            s = j + n - term.scr;
 671 +        }
 672 +        if (mode != SCROLL_RESIZE)
 673 +            tfulldirt();
 674 +    } else {
 675 +        tclearregion(0, top, term.col-1, top+n-1, 1);
 676 +        tsetdirt(top+n, bot);
 677      }
 678  
 679 -    if (term.scr > 0 && term.scr < HISTSIZE)
 680 -        term.scr = MIN(term.scr + n, HISTSIZE-1);
 681 -
 682 -    tclearregion(0, orig, term.col-1, orig+n-1);
 683 -    tsetdirt(orig+n, term.bot);
 684 -
 685 -    for (i = orig; i <= term.bot-n; i++) {
 686 +    for (i = top; i <= bot-n; i++) {
 687          temp = term.line[i];
 688          term.line[i] = term.line[i+n];
 689          term.line[i+n] = temp;
 690      }
 691  
 692 -    if (term.scr == 0)
 693 -        selscroll(orig, -n);
 694 +    if (sel.ob.x != -1 && sel.alt == alt) {
 695 +        if (!savehist) {
 696 +            selscroll(top, bot, -n);
 697 +        } else if (s > 0) {
 698 +            selmove(-s);
 699 +            if (-term.scr + sel.nb.y < -term.histf)
 700 +                selremove();
 701 +        }
 702 +    }
 703  }
 704  
 705  void
 706 -selscroll(int orig, int n)
 707 +selmove(int n)
 708  {
 709 -    if (sel.ob.x == -1)
 710 -        return;
 711 +    sel.ob.y += n, sel.nb.y += n;
 712 +    sel.oe.y += n, sel.ne.y += n;
 713 +}
 714 +
 715 +void
 716 +selscroll(int top, int bot, int n)
 717 +{
 718 +    /* turn absolute coordinates into relative */
 719 +    top += term.scr, bot += term.scr;
 720  
 721 -    if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
 722 +    if (BETWEEN(sel.nb.y, top, bot) != BETWEEN(sel.ne.y, top, bot)) {
 723          selclear();
 724 -    } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
 725 -        sel.ob.y += n;
 726 -        sel.oe.y += n;
 727 -        if (sel.ob.y < term.top || sel.ob.y > term.bot ||
 728 -            sel.oe.y < term.top || sel.oe.y > term.bot) {
 729 +    } else if (BETWEEN(sel.nb.y, top, bot)) {
 730 +        selmove(n);
 731 +        if (sel.nb.y < top || sel.ne.y > bot)
 732              selclear();
 733 -        } else {
 734 -            selnormalize();
 735 -        }
 736      }
 737  }
 738  
 739 @@ -1182,7 +1345,7 @@ tnewline(int first_col)
 740      int y = term.c.y;
 741  
 742      if (y == term.bot) {
 743 -        tscrollup(term.top, 1, 1);
 744 +        tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST);
 745      } else {
 746          y++;
 747      }
 748 @@ -1272,89 +1435,93 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
 749      } else if (term.line[y][x].mode & ATTR_WDUMMY) {
 750          term.line[y][x-1].u = ' ';
 751          term.line[y][x-1].mode &= ~ATTR_WIDE;
 752 -    }
 753 +  }
 754  
 755      term.dirty[y] = 1;
 756      term.line[y][x] = *attr;
 757      term.line[y][x].u = u;
 758 +    term.line[y][x].mode |= ATTR_SET;
 759  }
 760  
 761  void
 762 -tclearregion(int x1, int y1, int x2, int y2)
 763 +tclearglyph(Glyph *gp, int usecurattr)
 764  {
 765 -    int x, y, temp;
 766 -    Glyph *gp;
 767 +    if (usecurattr) {
 768 +        gp->fg = term.c.attr.fg;
 769 +        gp->bg = term.c.attr.bg;
 770 +    } else {
 771 +        gp->fg = defaultfg;
 772 +        gp->bg = defaultbg;
 773 +    }
 774 +    gp->mode = ATTR_NULL;
 775 +    gp->u = ' ';
 776 +}
 777  
 778 -    if (x1 > x2)
 779 -        temp = x1, x1 = x2, x2 = temp;
 780 -    if (y1 > y2)
 781 -        temp = y1, y1 = y2, y2 = temp;
 782 +void
 783 +tclearregion(int x1, int y1, int x2, int y2, int usecurattr)
 784 +{
 785 +    int x, y;
 786  
 787 -    LIMIT(x1, 0, term.col-1);
 788 -    LIMIT(x2, 0, term.col-1);
 789 -    LIMIT(y1, 0, term.row-1);
 790 -    LIMIT(y2, 0, term.row-1);
 791 +    /* regionselected() takes relative coordinates */
 792 +    if (regionselected(x1+term.scr, y1+term.scr, x2+term.scr, y2+term.scr))
 793 +        selremove();
 794  
 795      for (y = y1; y <= y2; y++) {
 796          term.dirty[y] = 1;
 797 -        for (x = x1; x <= x2; x++) {
 798 -            gp = &term.line[y][x];
 799 -            if (selected(x, y))
 800 -                selclear();
 801 -            gp->fg = term.c.attr.fg;
 802 -            gp->bg = term.c.attr.bg;
 803 -            gp->mode = 0;
 804 -            gp->u = ' ';
 805 -        }
 806 +        for (x = x1; x <= x2; x++)
 807 +            tclearglyph(&term.line[y][x], usecurattr);
 808      }
 809  }
 810  
 811  void
 812  tdeletechar(int n)
 813  {
 814 -    int dst, src, size;
 815 -    Glyph *line;
 816 -
 817 -    LIMIT(n, 0, term.col - term.c.x);
 818 +    int src, dst, size;
 819 +    Line line;
 820  
 821 +    if (n <= 0)
 822 +        return;
 823      dst = term.c.x;
 824 -    src = term.c.x + n;
 825 +    src = MIN(term.c.x + n, term.col);
 826      size = term.col - src;
 827 -    line = term.line[term.c.y];
 828 -
 829 -    memmove(&line[dst], &line[src], size * sizeof(Glyph));
 830 -    tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
 831 +    if (size > 0) { /* otherwise src would point beyond the array
 832 +                       https://stackoverflow.com/questions/29844298 */
 833 +        line = term.line[term.c.y];
 834 +        memmove(&line[dst], &line[src], size * sizeof(Glyph));
 835 +    }
 836 +    tclearregion(dst + size, term.c.y, term.col - 1, term.c.y, 1);
 837  }
 838  
 839  void
 840  tinsertblank(int n)
 841  {
 842 -    int dst, src, size;
 843 -    Glyph *line;
 844 +    int src, dst, size;
 845 +    Line line;
 846  
 847 -    LIMIT(n, 0, term.col - term.c.x);
 848 -
 849 -    dst = term.c.x + n;
 850 +    if (n <= 0)
 851 +        return;
 852 +    dst = MIN(term.c.x + n, term.col);
 853      src = term.c.x;
 854      size = term.col - dst;
 855 -    line = term.line[term.c.y];
 856 -
 857 -    memmove(&line[dst], &line[src], size * sizeof(Glyph));
 858 -    tclearregion(src, term.c.y, dst - 1, term.c.y);
 859 +    if (size > 0) { /* otherwise dst would point beyond the array */
 860 +        line = term.line[term.c.y];
 861 +        memmove(&line[dst], &line[src], size * sizeof(Glyph));
 862 +    }
 863 +    tclearregion(src, term.c.y, dst - 1, term.c.y, 1);
 864  }
 865  
 866  void
 867  tinsertblankline(int n)
 868  {
 869      if (BETWEEN(term.c.y, term.top, term.bot))
 870 -        tscrolldown(term.c.y, n, 0);
 871 +        tscrolldown(term.c.y, n);
 872  }
 873  
 874  void
 875  tdeleteline(int n)
 876  {
 877      if (BETWEEN(term.c.y, term.top, term.bot))
 878 -        tscrollup(term.c.y, n, 0);
 879 +        tscrollup(term.c.y, term.bot, n, SCROLL_NOSAVEHIST);
 880  }
 881  
 882  int32_t
 883 @@ -1528,7 +1695,7 @@ tsetscroll(int t, int b)
 884  void
 885  tsetmode(int priv, int set, const int *args, int narg)
 886  {
 887 -    int alt; const int *lim;
 888 +    const int *lim;
 889  
 890      for (lim = args + narg; args < lim; ++args) {
 891          if (priv) {
 892 @@ -1589,25 +1756,18 @@ tsetmode(int priv, int set, const int *args, int narg)
 893                  xsetmode(set, MODE_8BIT);
 894                  break;
 895              case 1049: /* swap screen & set/restore cursor as xterm */
 896 -                if (!allowaltscreen)
 897 -                    break;
 898 -                tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
 899 -                /* FALLTHROUGH */
 900              case 47: /* swap screen */
 901 -            case 1047:
 902 +            case 1047: /* swap screen, clearing alternate screen */
 903                  if (!allowaltscreen)
 904                      break;
 905 -                alt = IS_SET(MODE_ALTSCREEN);
 906 -                if (alt) {
 907 -                    tclearregion(0, 0, term.col-1,
 908 -                            term.row-1);
 909 -                }
 910 -                if (set ^ alt) /* set is always 1 or 0 */
 911 -                    tswapscreen();
 912 -                if (*args != 1049)
 913 -                    break;
 914 -                /* FALLTHROUGH */
 915 +                if (set)
 916 +                    tloadaltscreen(*args == 1049, *args == 1049);
 917 +                else
 918 +                    tloaddefscreen(*args == 1047, *args == 1049);
 919 +                break;
 920              case 1048:
 921 +                if (!allowaltscreen)
 922 +          break;
 923                  tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
 924                  break;
 925              case 2004: /* 2004: bracketed paste mode */
 926 @@ -1659,7 +1819,7 @@ void
 927  csihandle(void)
 928  {
 929      char buf[40];
 930 -    int len;
 931 +    int n, x;
 932  
 933      switch (csiescseq.mode[0]) {
 934      default:
 935 @@ -1757,20 +1917,30 @@ csihandle(void)
 936      case 'J': /* ED -- Clear screen */
 937          switch (csiescseq.arg[0]) {
 938          case 0: /* below */
 939 -            tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
 940 +            tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1);
 941              if (term.c.y < term.row-1) {
 942 -                tclearregion(0, term.c.y+1, term.col-1,
 943 -                        term.row-1);
 944 +                tclearregion(0, term.c.y+1, term.col-1, term.row-1, 1);
 945              }
 946              break;
 947          case 1: /* above */
 948 -            if (term.c.y > 1)
 949 -                tclearregion(0, 0, term.col-1, term.c.y-1);
 950 -            tclearregion(0, term.c.y, term.c.x, term.c.y);
 951 +            if (term.c.y >= 1)
 952 +                tclearregion(0, 0, term.col-1, term.c.y-1, 1);
 953 +            tclearregion(0, term.c.y, term.c.x, term.c.y, 1);
 954              break;
 955          case 2: /* all */
 956 -            tclearregion(0, 0, term.col-1, term.row-1);
 957 -            break;
 958 +            if (IS_SET(MODE_ALTSCREEN)) {
 959 +              tclearregion(0, 0, term.col-1, term.row-1, 1);
 960 +              break;
 961 +      }
 962 +            /* vte does this:
 963 +            tscrollup(0, term.row-1, term.row, SCROLL_SAVEHIST); */
 964 +      
 965 +            /* alacritty does this: */
 966 +            for (n = term.row-1; n >= 0 && tlinelen(term.line[n]) == 0; n--);
 967 +            if (n >= 0)
 968 +                tscrollup(0, term.row-1, n+1, SCROLL_SAVEHIST);
 969 +            tscrollup(0, term.row-1, term.row-n-1, SCROLL_NOSAVEHIST);
 970 +      break;
 971          default:
 972              goto unknown;
 973          }
 974 @@ -1778,24 +1948,24 @@ csihandle(void)
 975      case 'K': /* EL -- Clear line */
 976          switch (csiescseq.arg[0]) {
 977          case 0: /* right */
 978 -            tclearregion(term.c.x, term.c.y, term.col-1,
 979 -                    term.c.y);
 980 +            tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1);
 981              break;
 982          case 1: /* left */
 983 -            tclearregion(0, term.c.y, term.c.x, term.c.y);
 984 +            tclearregion(0, term.c.y, term.c.x, term.c.y, 1);
 985              break;
 986          case 2: /* all */
 987 -            tclearregion(0, term.c.y, term.col-1, term.c.y);
 988 +            tclearregion(0, term.c.y, term.col-1, term.c.y, 1);
 989              break;
 990          }
 991          break;
 992      case 'S': /* SU -- Scroll <n> line up */
 993          DEFAULT(csiescseq.arg[0], 1);
 994 -        tscrollup(term.top, csiescseq.arg[0], 0);
 995 +        /* xterm, urxvt, alacritty save this in history */
 996 +        tscrollup(term.top, term.bot, csiescseq.arg[0], SCROLL_SAVEHIST);
 997          break;
 998      case 'T': /* SD -- Scroll <n> line down */
 999          DEFAULT(csiescseq.arg[0], 1);
1000 -        tscrolldown(term.top, csiescseq.arg[0], 0);
1001 +        tscrolldown(term.top, csiescseq.arg[0]);
1002          break;
1003      case 'L': /* IL -- Insert <n> blank lines */
1004          DEFAULT(csiescseq.arg[0], 1);
1005 @@ -1809,9 +1979,11 @@ csihandle(void)
1006          tdeleteline(csiescseq.arg[0]);
1007          break;
1008      case 'X': /* ECH -- Erase <n> char */
1009 +        if (csiescseq.arg[0] < 0)
1010 +            return;
1011          DEFAULT(csiescseq.arg[0], 1);
1012 -        tclearregion(term.c.x, term.c.y,
1013 -                term.c.x + csiescseq.arg[0] - 1, term.c.y);
1014 +        x = MIN(term.c.x + csiescseq.arg[0], term.col) - 1;
1015 +        tclearregion(term.c.x, term.c.y, x, term.c.y, 1);
1016          break;
1017      case 'P': /* DCH -- Delete <n> char */
1018          DEFAULT(csiescseq.arg[0], 1);
1019 @@ -1833,9 +2005,9 @@ csihandle(void)
1020          break;
1021      case 'n': /* DSR – Device Status Report (cursor position) */
1022          if (csiescseq.arg[0] == 6) {
1023 -            len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
1024 +            n = snprintf(buf, sizeof(buf), "\033[%i;%iR",
1025                      term.c.y+1, term.c.x+1);
1026 -            ttywrite(buf, len, 0);
1027 +            ttywrite(buf, n, 0);
1028          }
1029          break;
1030      case 'r': /* DECSTBM -- Set Scrolling Region */
1031 @@ -2128,16 +2300,8 @@ tdumpsel(void)
1032  void
1033  tdumpline(int n)
1034  {
1035 -    char buf[UTF_SIZ];
1036 -    const Glyph *bp, *end;
1037 -
1038 -    bp = &term.line[n][0];
1039 -    end = &bp[MIN(tlinelen(n), term.col) - 1];
1040 -    if (bp != end || bp->u != ' ') {
1041 -        for ( ; bp <= end; ++bp)
1042 -            tprinter(buf, utf8encode(bp->u, buf));
1043 -    }
1044 -    tprinter("\n", 1);
1045 +    char str[(term.col + 1) * UTF_SIZ];
1046 +  tprinter(str, tgetline(str, &term.line[n][0]));
1047  }
1048  
1049  void
1050 @@ -2358,7 +2522,7 @@ eschandle(uchar ascii)
1051          return 0;
1052      case 'D': /* IND -- Linefeed */
1053          if (term.c.y == term.bot) {
1054 -            tscrollup(term.top, 1, 1);
1055 +            tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST);
1056          } else {
1057              tmoveto(term.c.x, term.c.y+1);
1058          }
1059 @@ -2371,7 +2535,7 @@ eschandle(uchar ascii)
1060          break;
1061      case 'M': /* RI -- Reverse index */
1062          if (term.c.y == term.top) {
1063 -            tscrolldown(term.top, 1, 1);
1064 +            tscrolldown(term.top, 1);
1065          } else {
1066              tmoveto(term.c.x, term.c.y-1);
1067          }
1068 @@ -2511,7 +2675,8 @@ check_control_code:
1069           */
1070          return;
1071      }
1072 -    if (selected(term.c.x, term.c.y))
1073 +    /* selected() takes relative coordinates */
1074 +    if (selected(term.c.x + term.scr, term.c.y + term.scr))
1075          selclear();
1076  
1077      gp = &term.line[term.c.y][term.c.x];
1078 @@ -2546,6 +2711,7 @@ check_control_code:
1079      if (term.c.x+width < term.col) {
1080          tmoveto(term.c.x+width, term.c.y);
1081      } else {
1082 +        term.wrapcwidth[IS_SET(MODE_ALTSCREEN)] = width;
1083          term.c.state |= CURSOR_WRAPNEXT;
1084      }
1085  }
1086 @@ -2583,93 +2749,275 @@ twrite(const char *buf, int buflen, int show_ctrl)
1087  }
1088  
1089  void
1090 -tresize(int col, int row)
1091 +treflow(int col, int row)
1092  {
1093      int i, j;
1094 -    int minrow = MIN(row, term.row);
1095 -    int mincol = MIN(col, term.col);
1096 -    int *bp;
1097 -    TCursor c;
1098 -
1099 -    if (col < 1 || row < 1) {
1100 -        fprintf(stderr,
1101 -                "tresize: error resizing to %dx%d\n", col, row);
1102 -        return;
1103 +    int oce, nce, bot, scr;
1104 +    int ox = 0, oy = -term.histf, nx = 0, ny = -1, len;
1105 +    int cy = -1; /* proxy for new y coordinate of cursor */
1106 +    int nlines;
1107 +    Line *buf, line;
1108 +
1109 +    /* y coordinate of cursor line end */
1110 +    for (oce = term.c.y; oce < term.row - 1 &&
1111 +                         tiswrapped(term.line[oce]); oce++);
1112 +
1113 +    nlines = term.histf + oce + 1;
1114 +    if (col < term.col) {
1115 +        /* each line can take this many lines after reflow */
1116 +        j = (term.col + col - 1) / col;
1117 +        nlines = j * nlines;
1118 +        if (nlines > HISTSIZE + RESIZEBUFFER + row) {
1119 +            nlines = HISTSIZE + RESIZEBUFFER + row;
1120 +            oy = -(nlines / j - oce - 1);
1121 +        }
1122      }
1123 +    buf = xmalloc(nlines * sizeof(Line));
1124 +    do {
1125 +        if (!nx)
1126 +            buf[++ny] = xmalloc(col * sizeof(Glyph));
1127 +        if (!ox) {
1128 +            line = TLINEABS(oy);
1129 +            len = tlinelen(line);
1130 +        }
1131 +        if (oy == term.c.y) {
1132 +            if (!ox)
1133 +                len = MAX(len, term.c.x + 1);
1134 +            /* update cursor */
1135 +            if (cy < 0 && term.c.x - ox < col - nx) {
1136 +                term.c.x = nx + term.c.x - ox, cy = ny;
1137 +                UPDATEWRAPNEXT(0, col);
1138 +            }
1139 +        }
1140 +        /* get reflowed lines in buf */
1141 +        if (col - nx > len - ox) {
1142 +            memcpy(&buf[ny][nx], &line[ox], (len-ox) * sizeof(Glyph));
1143 +            nx += len - ox;
1144 +            if (len == 0 || !(line[len - 1].mode & ATTR_WRAP)) {
1145 +                for (j = nx; j < col; j++)
1146 +                    tclearglyph(&buf[ny][j], 0);
1147 +                nx = 0;
1148 +            } else if (nx > 0) {
1149 +                buf[ny][nx - 1].mode &= ~ATTR_WRAP;
1150 +            }
1151 +            ox = 0, oy++;
1152 +        } else if (col - nx == len - ox) {
1153 +            memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph));
1154 +            ox = 0, oy++, nx = 0;
1155 +        } else/* if (col - nx < len - ox) */ {
1156 +            memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph));
1157 +        ox += col - nx;
1158 +            buf[ny][col - 1].mode |= ATTR_WRAP;
1159 +            nx = 0;
1160 +        }
1161 +    } while (oy <= oce);
1162 +    if (nx)
1163 +        for (j = nx; j < col; j++)
1164 +            tclearglyph(&buf[ny][j], 0);
1165  
1166 -    /*
1167 -     * slide screen to keep cursor where we expect it -
1168 -     * tscrollup would work here, but we can optimize to
1169 -     * memmove because we're freeing the earlier lines
1170 -     */
1171 -    for (i = 0; i <= term.c.y - row; i++) {
1172 +    /* free extra lines */
1173 +    for (i = row; i < term.row; i++)
1174          free(term.line[i]);
1175 -        free(term.alt[i]);
1176 +    /* resize to new height */
1177 +    term.line = xrealloc(term.line, row * sizeof(Line));
1178 +
1179 +    bot = MIN(ny, row - 1);
1180 +    scr = MAX(row - term.row, 0);
1181 +    /* update y coordinate of cursor line end */
1182 +    nce = MIN(oce + scr, bot);
1183 +    /* update cursor y coordinate */
1184 +    term.c.y = nce - (ny - cy);
1185 +    if (term.c.y < 0) {
1186 +        j = nce, nce = MIN(nce + -term.c.y, bot);
1187 +        term.c.y += nce - j;
1188 +        while (term.c.y < 0) {
1189 +            free(buf[ny--]);
1190 +            term.c.y++;
1191 +        }
1192      }
1193 -    /* ensure that both src and dst are not NULL */
1194 -    if (i > 0) {
1195 -        memmove(term.line, term.line + i, row * sizeof(Line));
1196 -        memmove(term.alt, term.alt + i, row * sizeof(Line));
1197 +    /* allocate new rows */
1198 +    for (i = row - 1; i > nce; i--) {
1199 +        term.line[i] = xmalloc(col * sizeof(Glyph));
1200 +        for (j = 0; j < col; j++)
1201 +            tclearglyph(&term.line[i][j], 0);
1202      }
1203 -    for (i += row; i < term.row; i++) {
1204 +    /* fill visible area */
1205 +    for (/*i = nce */; i >= term.row; i--, ny--)
1206 +        term.line[i] = buf[ny];
1207 +    for (/*i = term.row - 1 */; i >= 0; i--, ny--) {
1208          free(term.line[i]);
1209 -        free(term.alt[i]);
1210 +        term.line[i] = buf[ny];
1211 +    }
1212 +    /* fill lines in history buffer and update term.histf */
1213 +    for (/*i = -1 */; ny >= 0 && i >= -HISTSIZE; i--, ny--) {
1214 +        j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE;
1215 +        free(term.hist[j]);
1216 +        term.hist[j] = buf[ny];
1217      }
1218 +    term.histf = -i - 1;
1219 +    term.scr = MIN(term.scr, term.histf);
1220 +    /* resize rest of the history lines */
1221 +    for (/*i = -term.histf - 1 */; i >= -HISTSIZE; i--) {
1222 +        j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE;
1223 +        term.hist[j] = xrealloc(term.hist[j], col * sizeof(Glyph));
1224 +    }
1225 +    free(buf);
1226 +}
1227  
1228 -    /* resize to new height */
1229 -    term.line = xrealloc(term.line, row * sizeof(Line));
1230 -    term.alt  = xrealloc(term.alt,  row * sizeof(Line));
1231 -    term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
1232 -    term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
1233 +void
1234 +rscrolldown(int n)
1235 +{
1236 +    int i;
1237 +    Line temp;
1238  
1239 -    for (i = 0; i < HISTSIZE; i++) {
1240 -        term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
1241 -        for (j = mincol; j < col; j++) {
1242 -            term.hist[i][j] = term.c.attr;
1243 -            term.hist[i][j].u = ' ';
1244 -        }
1245 -    }
1246 +    /* can never be true as of now
1247 +    if (IS_SET(MODE_ALTSCREEN))
1248 +        return; */
1249  
1250 -    /* resize each row to new width, zero-pad if needed */
1251 -    for (i = 0; i < minrow; i++) {
1252 -        term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
1253 -        term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
1254 -    }
1255 +    if ((n = MIN(n, term.histf)) <= 0)
1256 +        return;
1257  
1258 -    /* allocate any new rows */
1259 -    for (/* i = minrow */; i < row; i++) {
1260 -        term.line[i] = xmalloc(col * sizeof(Glyph));
1261 -        term.alt[i] = xmalloc(col * sizeof(Glyph));
1262 +    for (i = term.c.y + n; i >= n; i--) {
1263 +        temp = term.line[i];
1264 +        term.line[i] = term.line[i-n];
1265 +        term.line[i-n] = temp;
1266      }
1267 +    for (/*i = n - 1 */; i >= 0; i--) {
1268 +        temp = term.line[i];
1269 +        term.line[i] = term.hist[term.histi];
1270 +        term.hist[term.histi] = temp;
1271 +        term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
1272 +    }
1273 +    term.c.y += n;
1274 +    term.histf -= n;
1275 +    if ((i = term.scr - n) >= 0) {
1276 +        term.scr = i;
1277 +    } else {
1278 +        term.scr = 0;
1279 +        if (sel.ob.x != -1 && !sel.alt)
1280 +            selmove(-i);
1281 +    }
1282 +}
1283 +
1284 +void
1285 +tresize(int col, int row)
1286 +{
1287 +    int *bp;
1288 +
1289 +    /* col and row are always MAX(_, 1)
1290 +    if (col < 1 || row < 1) {
1291 +        fprintf(stderr, "tresize: error resizing to %dx%d\n", col, row);
1292 +        return;
1293 +    } */
1294 +
1295 +    term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
1296 +    term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
1297      if (col > term.col) {
1298          bp = term.tabs + term.col;
1299 -
1300          memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
1301          while (--bp > term.tabs && !*bp)
1302              /* nothing */ ;
1303          for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
1304              *bp = 1;
1305      }
1306 -    /* update terminal size */
1307 -    term.col = col;
1308 -    term.row = row;
1309 -    /* reset scrolling region */
1310 -    tsetscroll(0, row-1);
1311 -    /* make use of the LIMIT in tmoveto */
1312 -    tmoveto(term.c.x, term.c.y);
1313 -    /* Clearing both screens (it makes dirty all lines) */
1314 -    c = term.c;
1315 -    for (i = 0; i < 2; i++) {
1316 -        if (mincol < col && 0 < minrow) {
1317 -            tclearregion(mincol, 0, col - 1, minrow - 1);
1318 +
1319 +    if (IS_SET(MODE_ALTSCREEN))
1320 +        tresizealt(col, row);
1321 +    else
1322 +        tresizedef(col, row);
1323 +}
1324 +
1325 +void
1326 +tresizedef(int col, int row)
1327 +{
1328 +    int i, j;
1329 +
1330 +    /* return if dimensions haven't changed */
1331 +    if (term.col == col && term.row == row) {
1332 +        tfulldirt();
1333 +        return;
1334 +    }
1335 +    if (col != term.col) {
1336 +        if (!sel.alt)
1337 +            selremove();
1338 +        treflow(col, row);
1339 +    } else {
1340 +        /* slide screen up if otherwise cursor would get out of the screen */
1341 +        if (term.c.y >= row) {
1342 +            tscrollup(0, term.row - 1, term.c.y - row + 1, SCROLL_RESIZE);
1343 +            term.c.y = row - 1;
1344          }
1345 -        if (0 < col && minrow < row) {
1346 -            tclearregion(0, minrow, col - 1, row - 1);
1347 +        for (i = row; i < term.row; i++)
1348 +            free(term.line[i]);
1349 +
1350 +        /* resize to new height */
1351 +        term.line = xrealloc(term.line, row * sizeof(Line));
1352 +        /* allocate any new rows */
1353 +        for (i = term.row; i < row; i++) {
1354 +            term.line[i] = xmalloc(col * sizeof(Glyph));
1355 +            for (j = 0; j < col; j++)
1356 +                tclearglyph(&term.line[i][j], 0);
1357          }
1358 -        tswapscreen();
1359 -        tcursor(CURSOR_LOAD);
1360 +        /* scroll down as much as height has increased */
1361 +        rscrolldown(row - term.row);
1362 +    }
1363 +    /* update terminal size */
1364 +    term.col = col, term.row = row;
1365 +    /* reset scrolling region */
1366 +    term.top = 0, term.bot = row - 1;
1367 +    /* dirty all lines */
1368 +    tfulldirt();
1369 +}
1370 +
1371 +void
1372 +tresizealt(int col, int row)
1373 +{
1374 +    int i, j;
1375 +
1376 +    /* return if dimensions haven't changed */
1377 +    if (term.col == col && term.row == row) {
1378 +        tfulldirt();
1379 +        return;
1380      }
1381 -    term.c = c;
1382 +    if (sel.alt)
1383 +        selremove();
1384 +    /* slide screen up if otherwise cursor would get out of the screen */
1385 +    for (i = 0; i <= term.c.y - row; i++)
1386 +        free(term.line[i]);
1387 +    if (i > 0) {
1388 +        /* ensure that both src and dst are not NULL */
1389 +        memmove(term.line, term.line + i, row * sizeof(Line));
1390 +        term.c.y = row - 1;
1391 +    }
1392 +    for (i += row; i < term.row; i++)
1393 +        free(term.line[i]);
1394 +    /* resize to new height */
1395 +    term.line = xrealloc(term.line, row * sizeof(Line));
1396 +    /* resize to new width */
1397 +    for (i = 0; i < MIN(row, term.row); i++) {
1398 +        term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
1399 +        for (j = term.col; j < col; j++)
1400 +            tclearglyph(&term.line[i][j], 0);
1401 +    }
1402 +    /* allocate any new rows */
1403 +    for (/*i = MIN(row, term.row) */; i < row; i++) {
1404 +        term.line[i] = xmalloc(col * sizeof(Glyph));
1405 +        for (j = 0; j < col; j++)
1406 +            tclearglyph(&term.line[i][j], 0);
1407 +    }
1408 +    /* update cursor */
1409 +    if (term.c.x >= col) {
1410 +        term.c.state &= ~CURSOR_WRAPNEXT;
1411 +        term.c.x = col - 1;
1412 +    } else {
1413 +        UPDATEWRAPNEXT(1, col);
1414 +    }
1415 +    /* update terminal size */
1416 +    term.col = col, term.row = row;
1417 +    /* reset scrolling region */
1418 +    term.top = 0, term.bot = row - 1;
1419 +    /* dirty all lines */
1420 +    tfulldirt();
1421  }
1422  
1423  void
1424 @@ -2709,9 +3057,8 @@ draw(void)
1425          cx--;
1426  
1427      drawregion(0, 0, term.col, term.row);
1428 -    if (term.scr == 0)
1429 -        xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
1430 -                term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
1431 +    xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
1432 +            term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
1433      term.ocx = cx;
1434      term.ocy = term.c.y;
1435      xfinishdraw();
1436 diff --git a/st.h b/st.h
1437 index 818a6f8..514ec08 100644
1438 --- a/st.h
1439 +++ b/st.h
1440 @@ -22,17 +22,19 @@
1441  
1442  enum glyph_attribute {
1443      ATTR_NULL       = 0,
1444 -    ATTR_BOLD       = 1 << 0,
1445 -    ATTR_FAINT      = 1 << 1,
1446 -    ATTR_ITALIC     = 1 << 2,
1447 -    ATTR_UNDERLINE  = 1 << 3,
1448 -    ATTR_BLINK      = 1 << 4,
1449 -    ATTR_REVERSE    = 1 << 5,
1450 -    ATTR_INVISIBLE  = 1 << 6,
1451 -    ATTR_STRUCK     = 1 << 7,
1452 -    ATTR_WRAP       = 1 << 8,
1453 -    ATTR_WIDE       = 1 << 9,
1454 -    ATTR_WDUMMY     = 1 << 10,
1455 +    ATTR_SET        = 1 << 0,
1456 +    ATTR_BOLD       = 1 << 1,
1457 +    ATTR_FAINT      = 1 << 2,
1458 +    ATTR_ITALIC     = 1 << 3,
1459 +    ATTR_UNDERLINE  = 1 << 4,
1460 +    ATTR_BLINK      = 1 << 5,
1461 +    ATTR_REVERSE    = 1 << 6,
1462 +    ATTR_INVISIBLE  = 1 << 7,
1463 +    ATTR_STRUCK     = 1 << 8,
1464 +    ATTR_WRAP       = 1 << 9,
1465 +    ATTR_WIDE       = 1 << 10,
1466 +    ATTR_WDUMMY     = 1 << 11,
1467 +    ATTR_SELECTED   = 1 << 12,
1468      ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
1469  };
1470  
1471 @@ -90,6 +92,7 @@ void toggleprinter(const Arg *);
1472  
1473  int tattrset(int);
1474  void tnew(int, int);
1475 +int tisaltscreen(void);
1476  void tresize(int, int);
1477  void tsetdirtattr(int);
1478  void ttyhangup(void);