sites

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

st-scrollback-reflow-0.9.2.diff (43790B)


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