st-scrollback-ringbuffer-0.8.5.diff - sites - public wiki contents of suckless.org
 (HTM) git clone git://git.suckless.org/sites
 (DIR) Log
 (DIR) Files
 (DIR) Refs
       ---
       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"