sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | Refs

st-scrollback-reflow-20230607-211964d.diff (38252B)


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