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