sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | 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"