st-scrollback-reflow-0.9.2.diff - sites - public wiki contents of suckless.org
 (HTM) git clone git://git.suckless.org/sites
 (DIR) Log
 (DIR) Files
 (DIR) 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);