st

fork of suckless's simple terminal
Index Commits Files Refs README LICENSE
patches/st-scrollback-ringbuffer-0.8.5.diff (19546B)
   1 commit 0663bdf11a409961da5b1120741a69814da8ce65
   2 Author: Timo Röhling <timo@gaussglocke.de>
   3 Date:   Tue Nov 23 19:45:33 2021 +0100
   4 
   5     Terminal scrollback with ring buffer
   6     
   7     This patch adds a ring buffer for scrollback to the terminal.  The
   8     advantage of using a ring buffer is that the common case, scrolling with
   9     no static screen content, can be achieved very efficiently by
  10     incrementing and decrementing the starting line (modulo buffer size).
  11     
  12     The scrollback buffer is limited to HISTSIZE lines in order to bound
  13     memory usage. As the lines are allocated on demand, it is possible to
  14     implement unlimited scrollback with few changes.  If the terminal is
  15     reset, the scroll back buffer is reset, too.
  16 
  17 diff --git a/config.def.h b/config.def.h
  18 index 91ab8ca..e3b469b 100644
  19 --- a/config.def.h
  20 +++ b/config.def.h
  21 @@ -201,6 +201,8 @@ static Shortcut shortcuts[] = {
  22      { TERMMOD,              XK_Y,           selpaste,       {.i =  0} },
  23      { ShiftMask,            XK_Insert,      selpaste,       {.i =  0} },
  24      { TERMMOD,              XK_Num_Lock,    numlock,        {.i =  0} },
  25 +    { ShiftMask,            XK_Page_Up,     kscrollup,      {.i = -1} },
  26 +    { ShiftMask,            XK_Page_Down,   kscrolldown,    {.i = -1} },
  27  };
  28  
  29  /*
  30 diff --git a/st.c b/st.c
  31 index 51049ba..f9e24ba 100644
  32 --- a/st.c
  33 +++ b/st.c
  34 @@ -43,6 +43,10 @@
  35  #define ISCONTROL(c)        (ISCONTROLC0(c) || ISCONTROLC1(c))
  36  #define ISDELIM(u)        (u && wcschr(worddelimiters, u))
  37  
  38 +#define TSCREEN term.screen[IS_SET(MODE_ALTSCREEN)]
  39 +#define TLINEOFFSET(y) (((y) + TSCREEN.cur - TSCREEN.off + TSCREEN.size) % TSCREEN.size)
  40 +#define TLINE(y) (TSCREEN.buffer[TLINEOFFSET(y)])
  41 +
  42  enum term_mode {
  43      MODE_WRAP        = 1 << 0,
  44      MODE_INSERT      = 1 << 1,
  45 @@ -109,12 +113,21 @@ typedef struct {
  46      int alt;
  47  } Selection;
  48  
  49 +/* Screen lines */
  50 +typedef struct {
  51 +    Line* buffer;  /* ring buffer */
  52 +    int size;      /* size of buffer */
  53 +    int cur;       /* start of active screen */
  54 +    int off;       /* scrollback line offset */
  55 +    TCursor sc;    /* saved cursor */
  56 +} LineBuffer;
  57 +
  58  /* Internal representation of the screen */
  59  typedef struct {
  60      int row;      /* nb row */
  61      int col;      /* nb col */
  62 -    Line *line;   /* screen */
  63 -    Line *alt;    /* alternate screen */
  64 +    LineBuffer screen[2]; /* screen and alternate screen */
  65 +    int linelen;  /* allocated line length */
  66      int *dirty;   /* dirtyness of lines */
  67      TCursor c;    /* cursor */
  68      int ocx;      /* old cursor col */
  69 @@ -202,6 +215,8 @@ static void tdeftran(char);
  70  static void tstrsequence(uchar);
  71  
  72  static void drawregion(int, int, int, int);
  73 +static void clearline(Line, Glyph, int, int);
  74 +static Line ensureline(Line);
  75  
  76  static void selnormalize(void);
  77  static void selscroll(int, int);
  78 @@ -415,11 +430,12 @@ int
  79  tlinelen(int y)
  80  {
  81      int i = term.col;
  82 +    Line line = TLINE(y);
  83  
  84 -    if (term.line[y][i - 1].mode & ATTR_WRAP)
  85 +    if (line[i - 1].mode & ATTR_WRAP)
  86          return i;
  87  
  88 -    while (i > 0 && term.line[y][i - 1].u == ' ')
  89 +    while (i > 0 && line[i - 1].u == ' ')
  90          --i;
  91  
  92      return i;
  93 @@ -528,7 +544,7 @@ selsnap(int *x, int *y, int direction)
  94           * Snap around if the word wraps around at the end or
  95           * beginning of a line.
  96           */
  97 -        prevgp = &term.line[*y][*x];
  98 +        prevgp = &TLINE(*y)[*x];
  99          prevdelim = ISDELIM(prevgp->u);
 100          for (;;) {
 101              newx = *x + direction;
 102 @@ -543,14 +559,14 @@ selsnap(int *x, int *y, int direction)
 103                      yt = *y, xt = *x;
 104                  else
 105                      yt = newy, xt = newx;
 106 -                if (!(term.line[yt][xt].mode & ATTR_WRAP))
 107 +                if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
 108                      break;
 109              }
 110  
 111              if (newx >= tlinelen(newy))
 112                  break;
 113  
 114 -            gp = &term.line[newy][newx];
 115 +            gp = &TLINE(newy)[newx];
 116              delim = ISDELIM(gp->u);
 117              if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
 118                      || (delim && gp->u != prevgp->u)))
 119 @@ -571,14 +587,14 @@ selsnap(int *x, int *y, int direction)
 120          *x = (direction < 0) ? 0 : term.col - 1;
 121          if (direction < 0) {
 122              for (; *y > 0; *y += direction) {
 123 -                if (!(term.line[*y-1][term.col-1].mode
 124 +                if (!(TLINE(*y-1)[term.col-1].mode
 125                          & ATTR_WRAP)) {
 126                      break;
 127                  }
 128              }
 129          } else if (direction > 0) {
 130              for (; *y < term.row-1; *y += direction) {
 131 -                if (!(term.line[*y][term.col-1].mode
 132 +                if (!(TLINE(*y)[term.col-1].mode
 133                          & ATTR_WRAP)) {
 134                      break;
 135                  }
 136 @@ -609,13 +625,13 @@ getsel(void)
 137          }
 138  
 139          if (sel.type == SEL_RECTANGULAR) {
 140 -            gp = &term.line[y][sel.nb.x];
 141 +            gp = &TLINE(y)[sel.nb.x];
 142              lastx = sel.ne.x;
 143          } else {
 144 -            gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
 145 +            gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
 146              lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
 147          }
 148 -        last = &term.line[y][MIN(lastx, linelen-1)];
 149 +        last = &TLINE(y)[MIN(lastx, linelen-1)];
 150          while (last >= gp && last->u == ' ')
 151              --last;
 152  
 153 @@ -956,12 +972,15 @@ int
 154  tattrset(int attr)
 155  {
 156      int i, j;
 157 +    int y = TLINEOFFSET(0);
 158  
 159      for (i = 0; i < term.row-1; i++) {
 160 +        Line line = TSCREEN.buffer[y];
 161          for (j = 0; j < term.col-1; j++) {
 162 -            if (term.line[i][j].mode & attr)
 163 +            if (line[j].mode & attr)
 164                  return 1;
 165          }
 166 +        y = (y+1) % TSCREEN.size;
 167      }
 168  
 169      return 0;
 170 @@ -983,14 +1002,17 @@ void
 171  tsetdirtattr(int attr)
 172  {
 173      int i, j;
 174 +    int y = TLINEOFFSET(0);
 175  
 176      for (i = 0; i < term.row-1; i++) {
 177 +        Line line = TSCREEN.buffer[y];
 178          for (j = 0; j < term.col-1; j++) {
 179 -            if (term.line[i][j].mode & attr) {
 180 +            if (line[j].mode & attr) {
 181                  tsetdirt(i, i);
 182                  break;
 183              }
 184          }
 185 +        y = (y+1) % TSCREEN.size;
 186      }
 187  }
 188  
 189 @@ -1003,27 +1025,19 @@ tfulldirt(void)
 190  void
 191  tcursor(int mode)
 192  {
 193 -    static TCursor c[2];
 194 -    int alt = IS_SET(MODE_ALTSCREEN);
 195 -
 196      if (mode == CURSOR_SAVE) {
 197 -        c[alt] = term.c;
 198 +        TSCREEN.sc = term.c;
 199      } else if (mode == CURSOR_LOAD) {
 200 -        term.c = c[alt];
 201 -        tmoveto(c[alt].x, c[alt].y);
 202 +        term.c = TSCREEN.sc;
 203 +        tmoveto(term.c.x, term.c.y);
 204      }
 205  }
 206  
 207  void
 208  treset(void)
 209  {
 210 -    uint i;
 211 -
 212 -    term.c = (TCursor){{
 213 -        .mode = ATTR_NULL,
 214 -        .fg = defaultfg,
 215 -        .bg = defaultbg
 216 -    }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
 217 +    int i, j;
 218 +    Glyph g = (Glyph){ .fg = defaultfg, .bg = defaultbg};
 219  
 220      memset(term.tabs, 0, term.col * sizeof(*term.tabs));
 221      for (i = tabspaces; i < term.col; i += tabspaces)
 222 @@ -1035,17 +1049,37 @@ treset(void)
 223      term.charset = 0;
 224  
 225      for (i = 0; i < 2; i++) {
 226 -        tmoveto(0, 0);
 227 -        tcursor(CURSOR_SAVE);
 228 -        tclearregion(0, 0, term.col-1, term.row-1);
 229 -        tswapscreen();
 230 +        term.screen[i].sc = (TCursor){{
 231 +            .fg = defaultfg,
 232 +            .bg = defaultbg
 233 +        }};
 234 +        term.screen[i].cur = 0;
 235 +        term.screen[i].off = 0;
 236 +        for (j = 0; j < term.row; ++j) {
 237 +            if (term.col != term.linelen)
 238 +                term.screen[i].buffer[j] = xrealloc(term.screen[i].buffer[j], term.col * sizeof(Glyph));
 239 +            clearline(term.screen[i].buffer[j], g, 0, term.col);
 240 +        }
 241 +        for (j = term.row; j < term.screen[i].size; ++j) {
 242 +            free(term.screen[i].buffer[j]);
 243 +            term.screen[i].buffer[j] = NULL;
 244 +        }
 245      }
 246 +    tcursor(CURSOR_LOAD);
 247 +    term.linelen = term.col;
 248 +    tfulldirt();
 249  }
 250  
 251  void
 252  tnew(int col, int row)
 253  {
 254 -    term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
 255 +    int i;
 256 +    term = (Term){};
 257 +    term.screen[0].buffer = xmalloc(HISTSIZE * sizeof(Line));
 258 +    term.screen[0].size = HISTSIZE;
 259 +    term.screen[1].buffer = NULL;
 260 +    for (i = 0; i < HISTSIZE; ++i) term.screen[0].buffer[i] = NULL;
 261 +
 262      tresize(col, row);
 263      treset();
 264  }
 265 @@ -1053,14 +1087,42 @@ tnew(int col, int row)
 266  void
 267  tswapscreen(void)
 268  {
 269 -    Line *tmp = term.line;
 270 -
 271 -    term.line = term.alt;
 272 -    term.alt = tmp;
 273      term.mode ^= MODE_ALTSCREEN;
 274      tfulldirt();
 275  }
 276  
 277 +void
 278 +kscrollup(const Arg *a)
 279 +{
 280 +    int n = a->i;
 281 +
 282 +    if (IS_SET(MODE_ALTSCREEN))
 283 +        return;
 284 +
 285 +    if (n < 0) n = (-n) * term.row;
 286 +    if (n > TSCREEN.size - term.row - TSCREEN.off) n = TSCREEN.size - term.row - TSCREEN.off;
 287 +    while (!TLINE(-n)) --n;
 288 +    TSCREEN.off += n;
 289 +    selscroll(0, n);
 290 +    tfulldirt();
 291 +}
 292 +
 293 +void
 294 +kscrolldown(const Arg *a)
 295 +{
 296 +
 297 +    int n = a->i;
 298 +
 299 +    if (IS_SET(MODE_ALTSCREEN))
 300 +        return;
 301 +
 302 +    if (n < 0) n = (-n) * term.row;
 303 +    if (n > TSCREEN.off) n = TSCREEN.off;
 304 +    TSCREEN.off -= n;
 305 +    selscroll(0, -n);
 306 +    tfulldirt();
 307 +}
 308 +
 309  void
 310  tscrolldown(int orig, int n)
 311  {
 312 @@ -1069,15 +1131,29 @@ tscrolldown(int orig, int n)
 313  
 314      LIMIT(n, 0, term.bot-orig+1);
 315  
 316 -    tsetdirt(orig, term.bot-n);
 317 -    tclearregion(0, term.bot-n+1, term.col-1, term.bot);
 318 +    /* Ensure that lines are allocated */
 319 +    for (i = -n; i < 0; i++) {
 320 +        TLINE(i) = ensureline(TLINE(i));
 321 +    }
 322  
 323 -    for (i = term.bot; i >= orig+n; i--) {
 324 -        temp = term.line[i];
 325 -        term.line[i] = term.line[i-n];
 326 -        term.line[i-n] = temp;
 327 +    /* Shift non-scrolling areas in ring buffer */
 328 +    for (i = term.bot+1; i < term.row; i++) {
 329 +        temp = TLINE(i);
 330 +        TLINE(i) = TLINE(i-n);
 331 +        TLINE(i-n) = temp;
 332 +    }
 333 +    for (i = 0; i < orig; i++) {
 334 +        temp = TLINE(i);
 335 +        TLINE(i) = TLINE(i-n);
 336 +        TLINE(i-n) = temp;
 337      }
 338  
 339 +    /* Scroll buffer */
 340 +    TSCREEN.cur = (TSCREEN.cur + TSCREEN.size - n) % TSCREEN.size;
 341 +    /* Clear lines that have entered the view */
 342 +    tclearregion(0, orig, term.linelen-1, orig+n-1);
 343 +    /* Redraw portion of the screen that has scrolled */
 344 +    tsetdirt(orig+n-1, term.bot);
 345      selscroll(orig, n);
 346  }
 347  
 348 @@ -1089,15 +1165,29 @@ tscrollup(int orig, int n)
 349  
 350      LIMIT(n, 0, term.bot-orig+1);
 351  
 352 -    tclearregion(0, orig, term.col-1, orig+n-1);
 353 -    tsetdirt(orig+n, term.bot);
 354 +    /* Ensure that lines are allocated */
 355 +    for (i = term.row; i < term.row + n; i++) {
 356 +        TLINE(i) = ensureline(TLINE(i));
 357 +    }
 358  
 359 -    for (i = orig; i <= term.bot-n; i++) {
 360 -        temp = term.line[i];
 361 -        term.line[i] = term.line[i+n];
 362 -        term.line[i+n] = temp;
 363 +    /* Shift non-scrolling areas in ring buffer */
 364 +    for (i = orig-1; i >= 0; i--) {
 365 +        temp = TLINE(i);
 366 +        TLINE(i) = TLINE(i+n);
 367 +        TLINE(i+n) = temp;
 368 +    }
 369 +    for (i = term.row-1; i >term.bot; i--) {
 370 +        temp = TLINE(i);
 371 +        TLINE(i) = TLINE(i+n);
 372 +        TLINE(i+n) = temp;
 373      }
 374  
 375 +    /* Scroll buffer */
 376 +    TSCREEN.cur = (TSCREEN.cur + n) % TSCREEN.size;
 377 +    /* Clear lines that have entered the view */
 378 +    tclearregion(0, term.bot-n+1, term.linelen-1, term.bot);
 379 +    /* Redraw portion of the screen that has scrolled */
 380 +    tsetdirt(orig, term.bot-n+1);
 381      selscroll(orig, -n);
 382  }
 383  
 384 @@ -1201,6 +1291,7 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
 385          "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
 386          "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
 387      };
 388 +    Line line = TLINE(y);
 389  
 390      /*
 391       * The table is proudly stolen from rxvt.
 392 @@ -1209,25 +1300,25 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
 393         BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
 394          utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
 395  
 396 -    if (term.line[y][x].mode & ATTR_WIDE) {
 397 +    if (line[x].mode & ATTR_WIDE) {
 398          if (x+1 < term.col) {
 399 -            term.line[y][x+1].u = ' ';
 400 -            term.line[y][x+1].mode &= ~ATTR_WDUMMY;
 401 +            line[x+1].u = ' ';
 402 +            line[x+1].mode &= ~ATTR_WDUMMY;
 403          }
 404 -    } else if (term.line[y][x].mode & ATTR_WDUMMY) {
 405 -        term.line[y][x-1].u = ' ';
 406 -        term.line[y][x-1].mode &= ~ATTR_WIDE;
 407 +    } else if (line[x].mode & ATTR_WDUMMY) {
 408 +        line[x-1].u = ' ';
 409 +        line[x-1].mode &= ~ATTR_WIDE;
 410      }
 411  
 412      term.dirty[y] = 1;
 413 -    term.line[y][x] = *attr;
 414 -    term.line[y][x].u = u;
 415 +    line[x] = *attr;
 416 +    line[x].u = u;
 417  }
 418  
 419  void
 420  tclearregion(int x1, int y1, int x2, int y2)
 421  {
 422 -    int x, y, temp;
 423 +    int x, y, L, S, temp;
 424      Glyph *gp;
 425  
 426      if (x1 > x2)
 427 @@ -1235,15 +1326,16 @@ tclearregion(int x1, int y1, int x2, int y2)
 428      if (y1 > y2)
 429          temp = y1, y1 = y2, y2 = temp;
 430  
 431 -    LIMIT(x1, 0, term.col-1);
 432 -    LIMIT(x2, 0, term.col-1);
 433 +    LIMIT(x1, 0, term.linelen-1);
 434 +    LIMIT(x2, 0, term.linelen-1);
 435      LIMIT(y1, 0, term.row-1);
 436      LIMIT(y2, 0, term.row-1);
 437  
 438 +    L = TLINEOFFSET(y1);
 439      for (y = y1; y <= y2; y++) {
 440          term.dirty[y] = 1;
 441          for (x = x1; x <= x2; x++) {
 442 -            gp = &term.line[y][x];
 443 +            gp = &TSCREEN.buffer[L][x];
 444              if (selected(x, y))
 445                  selclear();
 446              gp->fg = term.c.attr.fg;
 447 @@ -1251,6 +1343,7 @@ tclearregion(int x1, int y1, int x2, int y2)
 448              gp->mode = 0;
 449              gp->u = ' ';
 450          }
 451 +        L = (L + 1) % TSCREEN.size;
 452      }
 453  }
 454  
 455 @@ -1265,7 +1358,7 @@ tdeletechar(int n)
 456      dst = term.c.x;
 457      src = term.c.x + n;
 458      size = term.col - src;
 459 -    line = term.line[term.c.y];
 460 +    line = TLINE(term.c.y);
 461  
 462      memmove(&line[dst], &line[src], size * sizeof(Glyph));
 463      tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
 464 @@ -1282,7 +1375,7 @@ tinsertblank(int n)
 465      dst = term.c.x + n;
 466      src = term.c.x;
 467      size = term.col - dst;
 468 -    line = term.line[term.c.y];
 469 +    line = TLINE(term.c.y);
 470  
 471      memmove(&line[dst], &line[src], size * sizeof(Glyph));
 472      tclearregion(src, term.c.y, dst - 1, term.c.y);
 473 @@ -2103,7 +2196,7 @@ tdumpline(int n)
 474      char buf[UTF_SIZ];
 475      const Glyph *bp, *end;
 476  
 477 -    bp = &term.line[n][0];
 478 +    bp = &TLINE(n)[0];
 479      end = &bp[MIN(tlinelen(n), term.col) - 1];
 480      if (bp != end || bp->u != ' ') {
 481          for ( ; bp <= end; ++bp)
 482 @@ -2486,11 +2579,11 @@ check_control_code:
 483      if (selected(term.c.x, term.c.y))
 484          selclear();
 485  
 486 -    gp = &term.line[term.c.y][term.c.x];
 487 +    gp = &TLINE(term.c.y)[term.c.x];
 488      if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
 489          gp->mode |= ATTR_WRAP;
 490          tnewline(1);
 491 -        gp = &term.line[term.c.y][term.c.x];
 492 +        gp = &TLINE(term.c.y)[term.c.x];
 493      }
 494  
 495      if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
 496 @@ -2498,7 +2591,7 @@ check_control_code:
 497  
 498      if (term.c.x+width > term.col) {
 499          tnewline(1);
 500 -        gp = &term.line[term.c.y][term.c.x];
 501 +        gp = &TLINE(term.c.y)[term.c.x];
 502      }
 503  
 504      tsetchar(u, &term.c.attr, term.c.x, term.c.y);
 505 @@ -2529,6 +2622,11 @@ twrite(const char *buf, int buflen, int show_ctrl)
 506      Rune u;
 507      int n;
 508  
 509 +    if (TSCREEN.off) {
 510 +        TSCREEN.off = 0;
 511 +        tfulldirt();
 512 +    }
 513 +
 514      for (n = 0; n < buflen; n += charsize) {
 515          if (IS_SET(MODE_UTF8)) {
 516              /* process a complete utf8 char */
 517 @@ -2555,56 +2653,85 @@ twrite(const char *buf, int buflen, int show_ctrl)
 518  }
 519  
 520  void
 521 -tresize(int col, int row)
 522 +clearline(Line line, Glyph g, int x, int xend)
 523  {
 524      int i;
 525 +    g.mode = 0;
 526 +    g.u = ' ';
 527 +    for (i = x; i < xend; ++i) {
 528 +        line[i] = g;
 529 +    }
 530 +}
 531 +
 532 +Line
 533 +ensureline(Line line)
 534 +{
 535 +    if (!line) {
 536 +        line = xmalloc(term.linelen * sizeof(Glyph));
 537 +    }
 538 +    return line;
 539 +}
 540 +
 541 +void
 542 +tresize(int col, int row)
 543 +{
 544 +    int i, j;
 545      int minrow = MIN(row, term.row);
 546      int mincol = MIN(col, term.col);
 547 +    int linelen = MAX(col, term.linelen);
 548      int *bp;
 549 -    TCursor c;
 550  
 551 -    if (col < 1 || row < 1) {
 552 +    if (col < 1 || row < 1 || row > HISTSIZE) {
 553          fprintf(stderr,
 554                  "tresize: error resizing to %dx%d\n", col, row);
 555          return;
 556      }
 557  
 558 -    /*
 559 -     * slide screen to keep cursor where we expect it -
 560 -     * tscrollup would work here, but we can optimize to
 561 -     * memmove because we're freeing the earlier lines
 562 -     */
 563 -    for (i = 0; i <= term.c.y - row; i++) {
 564 -        free(term.line[i]);
 565 -        free(term.alt[i]);
 566 +    /* Shift buffer to keep the cursor where we expect it */
 567 +    if (row <= term.c.y) {
 568 +        term.screen[0].cur = (term.screen[0].cur - row + term.c.y + 1) % term.screen[0].size;
 569 +    }
 570 +
 571 +    /* Resize and clear line buffers as needed */
 572 +    if (linelen > term.linelen) {
 573 +        for (i = 0; i < term.screen[0].size; ++i) {
 574 +            if (term.screen[0].buffer[i]) {
 575 +                term.screen[0].buffer[i] = xrealloc(term.screen[0].buffer[i], linelen * sizeof(Glyph));
 576 +                clearline(term.screen[0].buffer[i], term.c.attr, term.linelen, linelen);
 577 +            }
 578 +        }
 579 +        for (i = 0; i < minrow; ++i) {
 580 +            term.screen[1].buffer[i] = xrealloc(term.screen[1].buffer[i], linelen * sizeof(Glyph));
 581 +            clearline(term.screen[1].buffer[i], term.c.attr, term.linelen, linelen);
 582 +        }
 583      }
 584 -    /* ensure that both src and dst are not NULL */
 585 -    if (i > 0) {
 586 -        memmove(term.line, term.line + i, row * sizeof(Line));
 587 -        memmove(term.alt, term.alt + i, row * sizeof(Line));
 588 +    /* Allocate all visible lines for regular line buffer */
 589 +    for (j = term.screen[0].cur, i = 0; i < row; ++i, j = (j + 1) % term.screen[0].size)
 590 +    {
 591 +        if (!term.screen[0].buffer[j]) {
 592 +            term.screen[0].buffer[j] = xmalloc(linelen * sizeof(Glyph));
 593 +        }
 594 +        if (i >= term.row) {
 595 +            clearline(term.screen[0].buffer[j], term.c.attr, 0, linelen);
 596 +        }
 597      }
 598 -    for (i += row; i < term.row; i++) {
 599 -        free(term.line[i]);
 600 -        free(term.alt[i]);
 601 +    /* Resize alt screen */
 602 +    term.screen[1].cur = 0;
 603 +    term.screen[1].size = row;
 604 +    for (i = row; i < term.row; ++i) {
 605 +        free(term.screen[1].buffer[i]);
 606 +    }
 607 +    term.screen[1].buffer = xrealloc(term.screen[1].buffer, row * sizeof(Line));
 608 +    for (i = term.row; i < row; ++i) {
 609 +        term.screen[1].buffer[i] = xmalloc(linelen * sizeof(Glyph));
 610 +        clearline(term.screen[1].buffer[i], term.c.attr, 0, linelen);
 611      }
 612  
 613      /* resize to new height */
 614 -    term.line = xrealloc(term.line, row * sizeof(Line));
 615 -    term.alt  = xrealloc(term.alt,  row * sizeof(Line));
 616      term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
 617      term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
 618  
 619 -    /* resize each row to new width, zero-pad if needed */
 620 -    for (i = 0; i < minrow; i++) {
 621 -        term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
 622 -        term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
 623 -    }
 624 -
 625 -    /* allocate any new rows */
 626 -    for (/* i = minrow */; i < row; i++) {
 627 -        term.line[i] = xmalloc(col * sizeof(Glyph));
 628 -        term.alt[i] = xmalloc(col * sizeof(Glyph));
 629 -    }
 630 +    /* fix tabstops */
 631      if (col > term.col) {
 632          bp = term.tabs + term.col;
 633  
 634 @@ -2614,26 +2741,16 @@ tresize(int col, int row)
 635          for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
 636              *bp = 1;
 637      }
 638 +
 639      /* update terminal size */
 640      term.col = col;
 641      term.row = row;
 642 +    term.linelen = linelen;
 643      /* reset scrolling region */
 644      tsetscroll(0, row-1);
 645      /* make use of the LIMIT in tmoveto */
 646      tmoveto(term.c.x, term.c.y);
 647 -    /* Clearing both screens (it makes dirty all lines) */
 648 -    c = term.c;
 649 -    for (i = 0; i < 2; i++) {
 650 -        if (mincol < col && 0 < minrow) {
 651 -            tclearregion(mincol, 0, col - 1, minrow - 1);
 652 -        }
 653 -        if (0 < col && minrow < row) {
 654 -            tclearregion(0, minrow, col - 1, row - 1);
 655 -        }
 656 -        tswapscreen();
 657 -        tcursor(CURSOR_LOAD);
 658 -    }
 659 -    term.c = c;
 660 +    tfulldirt();
 661  }
 662  
 663  void
 664 @@ -2645,14 +2762,15 @@ resettitle(void)
 665  void
 666  drawregion(int x1, int y1, int x2, int y2)
 667  {
 668 -    int y;
 669 +    int y, L;
 670  
 671 +    L = TLINEOFFSET(y1);
 672      for (y = y1; y < y2; y++) {
 673 -        if (!term.dirty[y])
 674 -            continue;
 675 -
 676 -        term.dirty[y] = 0;
 677 -        xdrawline(term.line[y], x1, y, x2);
 678 +        if (term.dirty[y]) {
 679 +            term.dirty[y] = 0;
 680 +            xdrawline(TSCREEN.buffer[L], x1, y, x2);
 681 +        }
 682 +        L = (L + 1) % TSCREEN.size;
 683      }
 684  }
 685  
 686 @@ -2667,14 +2785,15 @@ draw(void)
 687      /* adjust cursor position */
 688      LIMIT(term.ocx, 0, term.col-1);
 689      LIMIT(term.ocy, 0, term.row-1);
 690 -    if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
 691 +    if (TLINE(term.ocy)[term.ocx].mode & ATTR_WDUMMY)
 692          term.ocx--;
 693 -    if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
 694 +    if (TLINE(term.c.y)[cx].mode & ATTR_WDUMMY)
 695          cx--;
 696  
 697      drawregion(0, 0, term.col, term.row);
 698 -    xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
 699 -            term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
 700 +    if (TSCREEN.off == 0)
 701 +        xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx],
 702 +                term.ocx, term.ocy, TLINE(term.ocy)[term.ocx]);
 703      term.ocx = cx;
 704      term.ocy = term.c.y;
 705      xfinishdraw();
 706 diff --git a/st.h b/st.h
 707 index 519b9bd..b48e810 100644
 708 --- a/st.h
 709 +++ b/st.h
 710 @@ -19,6 +19,7 @@
 711  
 712  #define TRUECOLOR(r,g,b)    (1 << 24 | (r) << 16 | (g) << 8 | (b))
 713  #define IS_TRUECOL(x)        (1 << 24 & (x))
 714 +#define HISTSIZE            2000
 715  
 716  enum glyph_attribute {
 717      ATTR_NULL       = 0,
 718 diff --git a/x.c b/x.c
 719 index 8a16faa..1bb5853 100644
 720 --- a/x.c
 721 +++ b/x.c
 722 @@ -59,6 +59,8 @@ static void zoom(const Arg *);
 723  static void zoomabs(const Arg *);
 724  static void zoomreset(const Arg *);
 725  static void ttysend(const Arg *);
 726 +void kscrollup(const Arg *);
 727 +void kscrolldown(const Arg *);
 728  
 729  /* config.h for applying patches and the configuration. */
 730  #include "config.h"