sites

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

st-scrollback-reflow-0.8.5.diff (37784B)


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