diff --git a/st.c b/st.c index 79ee9ba..5170cd4 100644 --- a/st.c +++ b/st.c @@ -36,6 +36,7 @@ #define STR_BUF_SIZ ESC_BUF_SIZ #define STR_ARG_SIZ ESC_ARG_SIZ #define HISTSIZE 2000 +#define RESIZEBUFFER 1000 /* macros */ #define IS_SET(flag) ((term.mode & (flag)) != 0) @@ -43,9 +44,22 @@ #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) #define ISDELIM(u) (u && wcschr(worddelimiters, u)) -#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ - term.scr + HISTSIZE + 1) % HISTSIZE] : \ - term.line[(y) - term.scr]) + +#define TLINE(y) ( \ + (y) < term.scr ? term.hist[(term.histi + (y) - term.scr + 1 + HISTSIZE) % HISTSIZE] \ + : term.line[(y) - term.scr] \ +) + +#define TLINEABS(y) ( \ + (y) < 0 ? term.hist[(term.histi + (y) + 1 + HISTSIZE) % HISTSIZE] : term.line[(y)] \ +) + +#define UPDATEWRAPNEXT(alt, col) do { \ + if ((term.c.state & CURSOR_WRAPNEXT) && term.c.x + term.wrapcwidth[alt] < col) { \ + term.c.x += term.wrapcwidth[alt]; \ + term.c.state &= ~CURSOR_WRAPNEXT; \ + } \ +} while (0); enum term_mode { MODE_WRAP = 1 << 0, @@ -57,6 +71,12 @@ enum term_mode { MODE_UTF8 = 1 << 6, }; +enum scroll_mode { + SCROLL_RESIZE = -1, + SCROLL_NOSAVEHIST = 0, + SCROLL_SAVEHIST = 1 +}; + enum cursor_movement { CURSOR_SAVE, CURSOR_LOAD @@ -118,10 +138,11 @@ typedef struct { int row; /* nb row */ int col; /* nb col */ Line *line; /* screen */ - Line *alt; /* alternate screen */ Line hist[HISTSIZE]; /* history buffer */ - int histi; /* history index */ - int scr; /* scroll back */ + int histi; /* history index */ + int histf; /* nb history available */ + int scr; /* scroll back */ + int wrapcwidth[2]; /* used in updating WRAPNEXT when resizing */ int *dirty; /* dirtyness of lines */ TCursor c; /* cursor */ int ocx; /* old cursor col */ @@ -179,26 +200,37 @@ static void tprinter(char *, size_t); static void tdumpsel(void); static void tdumpline(int); static void tdump(void); -static void tclearregion(int, int, int, int); +static void tclearregion(int, int, int, int, int); static void tcursor(int); +static void tclearglyph(Glyph *, int); +static void tresetcursor(void); static void tdeletechar(int); static void tdeleteline(int); static void tinsertblank(int); static void tinsertblankline(int); -static int tlinelen(int); +static int tlinelen(Line len); +static int tiswrapped(Line line); +static char *tgetglyphs(char *, const Glyph *, const Glyph *); +static size_t tgetline(char *, const Glyph *); static void tmoveto(int, int); static void tmoveato(int, int); static void tnewline(int); static void tputtab(int); static void tputc(Rune); static void treset(void); -static void tscrollup(int, int, int); -static void tscrolldown(int, int, int); +static void tscrollup(int, int, int, int); +static void tscrolldown(int, int); +static void treflow(int, int); +static void rscrolldown(int); +static void tresizedef(int, int); +static void tresizealt(int, int); static void tsetattr(const int *, int); static void tsetchar(Rune, const Glyph *, int, int); static void tsetdirt(int, int); static void tsetscroll(int, int); static void tswapscreen(void); +static void tloaddefscreen(int, int); +static void tloadaltscreen(int, int); static void tsetmode(int, int, const int *, int); static int twrite(const char *, int, int); static void tfulldirt(void); @@ -212,7 +244,10 @@ static void tstrsequence(uchar); static void drawregion(int, int, int, int); static void selnormalize(void); -static void selscroll(int, int); +static void selscroll(int, int, int); +static void selmove(int); +static void selremove(void); +static int regionselected(int, int, int, int); static void selsnap(int *, int *, int); static size_t utf8decode(const char *, Rune *, size_t); @@ -412,17 +447,46 @@ selinit(void) } int -tlinelen(int y) +tlinelen(Line line) { - int i = term.col; + int i = term.col - 1; + + for (; i >= 0 && !(line[i].mode & (ATTR_SET | ATTR_WRAP)); i--); + return i + 1; +} - if (TLINE(y)[i - 1].mode & ATTR_WRAP) - return i; +int +tiswrapped(Line line) +{ + int len = tlinelen(line); - while (i > 0 && TLINE(y)[i - 1].u == ' ') - --i; + return len > 0 && (line[len - 1].mode & ATTR_WRAP); +} - return i; +char * +tgetglyphs(char *buf, const Glyph *gp, const Glyph *lgp) +{ + while (gp <= lgp) + if (gp->mode & ATTR_WDUMMY) { + gp++; + } else { + buf += utf8encode((gp++)->u, buf); + } + return buf; +} + +size_t +tgetline(char *buf, const Glyph *fgp) +{ + char *ptr; + const Glyph *lgp = &fgp[term.col - 1]; + + while (lgp > fgp && !(lgp->mode & (ATTR_SET | ATTR_WRAP))) + lgp--; + ptr = tgetglyphs(buf, fgp, lgp); + if (!(lgp->mode & ATTR_WRAP)) + *(ptr++) = '\n'; + return ptr - buf; } void @@ -462,10 +526,11 @@ selextend(int col, int row, int type, int done) sel.oe.x = col; sel.oe.y = row; - selnormalize(); sel.type = type; + selnormalize(); - if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + if (oldey != sel.oe.y || oldex != sel.oe.x || + oldtype != sel.type || sel.mode == SEL_EMPTY) tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); sel.mode = done ? SEL_IDLE : SEL_READY; @@ -492,36 +557,43 @@ selnormalize(void) /* expand selection over line breaks */ if (sel.type == SEL_RECTANGULAR) return; - i = tlinelen(sel.nb.y); - if (i < sel.nb.x) + + i = tlinelen(TLINE(sel.nb.y)); + if (sel.nb.x > i) sel.nb.x = i; - if (tlinelen(sel.ne.y) <= sel.ne.x) - sel.ne.x = term.col - 1; + if (sel.ne.x >= tlinelen(TLINE(sel.ne.y))) + sel.ne.x = term.col - 1; } int -selected(int x, int y) +regionselected(int x1, int y1, int x2, int y2) { - if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || - sel.alt != IS_SET(MODE_ALTSCREEN)) + if (sel.ob.x == -1 || sel.mode == SEL_EMPTY || + sel.alt != IS_SET(MODE_ALTSCREEN) || sel.nb.y > y2 || sel.ne.y < y1) return 0; - if (sel.type == SEL_RECTANGULAR) - return BETWEEN(y, sel.nb.y, sel.ne.y) - && BETWEEN(x, sel.nb.x, sel.ne.x); + return (sel.type == SEL_RECTANGULAR) ? sel.nb.x <= x2 && sel.ne.x >= x1 + : (sel.nb.y != y2 || sel.nb.x <= x2) && + (sel.ne.y != y1 || sel.ne.x >= x1); +} - return BETWEEN(y, sel.nb.y, sel.ne.y) - && (y != sel.nb.y || x >= sel.nb.x) - && (y != sel.ne.y || x <= sel.ne.x); +int +selected(int x, int y) +{ + return regionselected(x, y, x, y); } void selsnap(int *x, int *y, int direction) { int newx, newy, xt, yt; + int rtop = 0, rbot = term.row - 1; int delim, prevdelim; const Glyph *gp, *prevgp; + if (!IS_SET(MODE_ALTSCREEN)) + rtop += -term.histf + term.scr, rbot += term.scr; + switch (sel.snap) { case SNAP_WORD: /* @@ -536,7 +608,7 @@ selsnap(int *x, int *y, int direction) if (!BETWEEN(newx, 0, term.col - 1)) { newy += direction; newx = (newx + term.col) % term.col; - if (!BETWEEN(newy, 0, term.row - 1)) + if (!BETWEEN(newy, rtop, rbot)) break; if (direction > 0) @@ -547,13 +619,13 @@ selsnap(int *x, int *y, int direction) break; } - if (newx >= tlinelen(newy)) + if (newx >= tlinelen(TLINE(newy))) break; gp = &TLINE(newy)[newx]; delim = ISDELIM(gp->u); - if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim - || (delim && gp->u != prevgp->u))) + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim || + (delim && !(gp->u == ' ' && prevgp->u == ' ')))) break; *x = newx; @@ -570,18 +642,14 @@ selsnap(int *x, int *y, int direction) */ *x = (direction < 0) ? 0 : term.col - 1; if (direction < 0) { - for (; *y > 0; *y += direction) { - if (!(TLINE(*y-1)[term.col-1].mode - & ATTR_WRAP)) { + for (; *y > rtop; *y -= 1) { + if (!tiswrapped(TLINE(*y-1))) break; - } } } else if (direction > 0) { - for (; *y < term.row-1; *y += direction) { - if (!(TLINE(*y)[term.col-1].mode - & ATTR_WRAP)) { + for (; *y < rbot; *y += 1) { + if (!tiswrapped(TLINE(*y))) break; - } } } break; @@ -592,40 +660,34 @@ char * getsel(void) { char *str, *ptr; - int y, bufsize, lastx, linelen; - const Glyph *gp, *last; + int y, lastx, linelen; + const Glyph *gp, *lgp; - if (sel.ob.x == -1) + if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) return NULL; - bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; - ptr = str = xmalloc(bufsize); + str = xmalloc((term.col + 1) * (sel.ne.y - sel.nb.y + 1) * UTF_SIZ); + ptr = str; /* append every set & selected glyph to the selection */ for (y = sel.nb.y; y <= sel.ne.y; y++) { - if ((linelen = tlinelen(y)) == 0) { + Line line = TLINE(y); + + if ((linelen = tlinelen(line)) == 0) { *ptr++ = '\n'; continue; } if (sel.type == SEL_RECTANGULAR) { - gp = &TLINE(y)[sel.nb.x]; + gp = &line[sel.nb.x]; lastx = sel.ne.x; } else { - gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; + gp = &line[sel.nb.y == y ? sel.nb.x : 0]; lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; } - last = &TLINE(y)[MIN(lastx, linelen-1)]; - while (last >= gp && last->u == ' ') - --last; - - for ( ; gp <= last; ++gp) { - if (gp->mode & ATTR_WDUMMY) - continue; - - ptr += utf8encode(gp->u, ptr); - } + lgp = &line[MIN(lastx, linelen-1)]; + ptr = tgetglyphs(ptr, gp, lgp); /* * Copy and pasting of line endings is inconsistent * in the inconsistent terminal and GUI world. @@ -636,10 +698,10 @@ getsel(void) * FIXME: Fix the computer world. */ if ((y < sel.ne.y || lastx >= linelen) && - (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + (!(lgp->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) *ptr++ = '\n'; } - *ptr = 0; + *ptr = '\0'; return str; } @@ -648,9 +710,15 @@ selclear(void) { if (sel.ob.x == -1) return; + selremove(); + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selremove(void) +{ sel.mode = SEL_IDLE; sel.ob.x = -1; - tsetdirt(sel.nb.y, sel.ne.y); } void @@ -851,10 +919,8 @@ void ttywrite(const char *s, size_t n, int may_echo) { const char *next; - Arg arg = (Arg) { .i = term.scr }; - - kscrolldown(&arg); + kscrolldown(&((Arg){ .i = term.scr })); if (may_echo && IS_SET(MODE_ECHO)) twrite(s, n, 1); @@ -990,7 +1056,7 @@ tsetdirtattr(int attr) for (i = 0; i < term.row-1; i++) { for (j = 0; j < term.col-1; j++) { if (term.line[i][j].mode & attr) { - tsetdirt(i, i); + term.dirty[i] = 1; break; } } @@ -1000,7 +1066,8 @@ tsetdirtattr(int attr) void tfulldirt(void) { - tsetdirt(0, term.row-1); + for (int i = 0; i < term.row; i++) + term.dirty[i] = 1; } void @@ -1017,51 +1084,116 @@ tcursor(int mode) } } +void +tresetcursor(void) +{ + term.c = (TCursor){ { .mode = ATTR_NULL, .fg = defaultfg, .bg = defaultbg }, + .x = 0, .y = 0, .state = CURSOR_DEFAULT }; +} + void treset(void) { uint i; + int x, y; - term.c = (TCursor){{ - .mode = ATTR_NULL, - .fg = defaultfg, - .bg = defaultbg - }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + tresetcursor(); memset(term.tabs, 0, term.col * sizeof(*term.tabs)); for (i = tabspaces; i < term.col; i += tabspaces) term.tabs[i] = 1; term.top = 0; + term.histf = 0; + term.scr = 0; term.bot = term.row - 1; term.mode = MODE_WRAP|MODE_UTF8; memset(term.trantbl, CS_USA, sizeof(term.trantbl)); term.charset = 0; + selremove(); for (i = 0; i < 2; i++) { - tmoveto(0, 0); - tcursor(CURSOR_SAVE); - tclearregion(0, 0, term.col-1, term.row-1); + tcursor(CURSOR_SAVE); /* reset saved cursor */ + for (y = 0; y < term.row; y++) + for (x = 0; x < term.col; x++) + tclearglyph(&term.line[y][x], 0); tswapscreen(); } + tfulldirt(); } void tnew(int col, int row) { - term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; - tresize(col, row); - treset(); + int i, j; + + for (i = 0; i < 2; i++) { + term.line = xmalloc(row * sizeof(Line)); + for (j = 0; j < row; j++) + term.line[j] = xmalloc(col * sizeof(Glyph)); + term.col = col, term.row = row; + tswapscreen(); + } + term.dirty = xmalloc(row * sizeof(*term.dirty)); + term.tabs = xmalloc(col * sizeof(*term.tabs)); + for (i = 0; i < HISTSIZE; i++) + term.hist[i] = xmalloc(col * sizeof(Glyph)); + treset(); } +/* handle it with care */ void tswapscreen(void) { - Line *tmp = term.line; + static Line *altline; + static int altcol, altrow; + Line *tmpline = term.line; + int tmpcol = term.col, tmprow = term.row; - term.line = term.alt; - term.alt = tmp; + term.line = altline; + term.col = altcol, term.row = altrow; + altline = tmpline; + altcol = tmpcol, altrow = tmprow; term.mode ^= MODE_ALTSCREEN; - tfulldirt(); +} + +void +tloaddefscreen(int clear, int loadcursor) +{ + int col, row, alt = IS_SET(MODE_ALTSCREEN); + + if (alt) { + if (clear) + tclearregion(0, 0, term.col-1, term.row-1, 1); + col = term.col, row = term.row; + tswapscreen(); + } + if (loadcursor) + tcursor(CURSOR_LOAD); + if (alt) + tresizedef(col, row); +} + +void +tloadaltscreen(int clear, int savecursor) +{ + int col, row, def = !IS_SET(MODE_ALTSCREEN); + + if (savecursor) + tcursor(CURSOR_SAVE); + if (def) { + col = term.col, row = term.row; + tswapscreen(); + term.scr = 0; + tresizealt(col, row); + } + if (clear) + tclearregion(0, 0, term.col-1, term.row-1, 1); +} + +int +tisaltscreen(void) +{ + return IS_SET(MODE_ALTSCREEN); } void @@ -1069,17 +1201,22 @@ kscrolldown(const Arg* a) { int n = a->i; - if (n < 0) - n = term.row + n; + if (!term.scr || IS_SET(MODE_ALTSCREEN)) + return; - if (n > term.scr) - n = term.scr; + if (n < 0) + n = MAX(term.row / -n, 1); - if (term.scr > 0) { + if (n <= term.scr) { term.scr -= n; - selscroll(0, -n); - tfulldirt(); + } else { + n = term.scr; + term.scr = 0; } + + if (sel.ob.x != -1 && !sel.alt) + selmove(-n); /* negate change in term.scr */ + tfulldirt(); } void @@ -1087,92 +1224,118 @@ kscrollup(const Arg* a) { int n = a->i; + if (!term.histf || IS_SET(MODE_ALTSCREEN)) + return; + if (n < 0) - n = term.row + n; + n = MAX(term.row / -n, 1); - if (term.scr <= HISTSIZE-n) { + if (term.scr + n <= term.histf) { term.scr += n; - selscroll(0, n); - tfulldirt(); + } else { + n = term.histf - term.scr; + term.scr = term.histf; } + + if (sel.ob.x != -1 && !sel.alt) + selmove(n); /* negate change in term.scr */ + tfulldirt(); } void -tscrolldown(int orig, int n, int copyhist) +tscrolldown(int top, int n) { - int i; + int i, bot = term.bot; Line temp; - LIMIT(n, 0, term.bot-orig+1); - if (copyhist) { - term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; - temp = term.hist[term.histi]; - term.hist[term.histi] = term.line[term.bot]; - term.line[term.bot] = temp; - } - + if (n <= 0) + return; + n = MIN(n, bot-top+1); - tsetdirt(orig, term.bot-n); - tclearregion(0, term.bot-n+1, term.col-1, term.bot); + tsetdirt(top, bot-n); + tclearregion(0, bot-n+1, term.col-1, bot, 1); - for (i = term.bot; i >= orig+n; i--) { + for (i = bot; i >= top+n; i--) { temp = term.line[i]; term.line[i] = term.line[i-n]; term.line[i-n] = temp; } - if (term.scr == 0) - selscroll(orig, n); + if (sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN)) + selscroll(top, bot, n); } void -tscrollup(int orig, int n, int copyhist) +tscrollup(int top, int bot, int n, int mode) { - int i; + int i, j, s; + int alt = IS_SET(MODE_ALTSCREEN); + int savehist = !alt && top == 0 && mode != SCROLL_NOSAVEHIST; Line temp; - LIMIT(n, 0, term.bot-orig+1); - - if (copyhist) { - term.histi = (term.histi + 1) % HISTSIZE; - temp = term.hist[term.histi]; - term.hist[term.histi] = term.line[orig]; - term.line[orig] = temp; + if (n <= 0) + return; + n = MIN(n, bot-top+1); + + if (savehist) { + for (i = 0; i < n; i++) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + for (j = 0; j < term.col; j++) + tclearglyph(&temp[j], 1); + term.hist[term.histi] = term.line[i]; + term.line[i] = temp; + } + term.histf = MIN(term.histf + n, HISTSIZE); + s = n; + if (term.scr) { + j = term.scr; + term.scr = MIN(j + n, HISTSIZE); + s = j + n - term.scr; + } + if (mode != SCROLL_RESIZE) + tfulldirt(); + } else { + tclearregion(0, top, term.col-1, top+n-1, 1); + tsetdirt(top+n, bot); } - if (term.scr > 0 && term.scr < HISTSIZE) - term.scr = MIN(term.scr + n, HISTSIZE-1); - - tclearregion(0, orig, term.col-1, orig+n-1); - tsetdirt(orig+n, term.bot); - - for (i = orig; i <= term.bot-n; i++) { + for (i = top; i <= bot-n; i++) { temp = term.line[i]; term.line[i] = term.line[i+n]; term.line[i+n] = temp; } - if (term.scr == 0) - selscroll(orig, -n); + if (sel.ob.x != -1 && sel.alt == alt) { + if (!savehist) { + selscroll(top, bot, -n); + } else if (s > 0) { + selmove(-s); + if (-term.scr + sel.nb.y < -term.histf) + selremove(); + } + } } void -selscroll(int orig, int n) +selmove(int n) { - if (sel.ob.x == -1) - return; + sel.ob.y += n, sel.nb.y += n; + sel.oe.y += n, sel.ne.y += n; +} + +void +selscroll(int top, int bot, int n) +{ + /* turn absolute coordinates into relative */ + top += term.scr, bot += term.scr; - if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + if (BETWEEN(sel.nb.y, top, bot) != BETWEEN(sel.ne.y, top, bot)) { selclear(); - } else if (BETWEEN(sel.nb.y, orig, term.bot)) { - sel.ob.y += n; - sel.oe.y += n; - if (sel.ob.y < term.top || sel.ob.y > term.bot || - sel.oe.y < term.top || sel.oe.y > term.bot) { + } else if (BETWEEN(sel.nb.y, top, bot)) { + selmove(n); + if (sel.nb.y < top || sel.ne.y > bot) selclear(); - } else { - selnormalize(); - } } } @@ -1182,7 +1345,7 @@ tnewline(int first_col) int y = term.c.y; if (y == term.bot) { - tscrollup(term.top, 1, 1); + tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST); } else { y++; } @@ -1272,89 +1435,93 @@ tsetchar(Rune u, const Glyph *attr, int x, int y) } else if (term.line[y][x].mode & ATTR_WDUMMY) { term.line[y][x-1].u = ' '; term.line[y][x-1].mode &= ~ATTR_WIDE; - } + } term.dirty[y] = 1; term.line[y][x] = *attr; term.line[y][x].u = u; + term.line[y][x].mode |= ATTR_SET; } void -tclearregion(int x1, int y1, int x2, int y2) +tclearglyph(Glyph *gp, int usecurattr) { - int x, y, temp; - Glyph *gp; + if (usecurattr) { + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + } else { + gp->fg = defaultfg; + gp->bg = defaultbg; + } + gp->mode = ATTR_NULL; + gp->u = ' '; +} - if (x1 > x2) - temp = x1, x1 = x2, x2 = temp; - if (y1 > y2) - temp = y1, y1 = y2, y2 = temp; +void +tclearregion(int x1, int y1, int x2, int y2, int usecurattr) +{ + int x, y; - LIMIT(x1, 0, term.col-1); - LIMIT(x2, 0, term.col-1); - LIMIT(y1, 0, term.row-1); - LIMIT(y2, 0, term.row-1); + /* regionselected() takes relative coordinates */ + if (regionselected(x1+term.scr, y1+term.scr, x2+term.scr, y2+term.scr)) + selremove(); for (y = y1; y <= y2; y++) { term.dirty[y] = 1; - for (x = x1; x <= x2; x++) { - gp = &term.line[y][x]; - if (selected(x, y)) - selclear(); - gp->fg = term.c.attr.fg; - gp->bg = term.c.attr.bg; - gp->mode = 0; - gp->u = ' '; - } + for (x = x1; x <= x2; x++) + tclearglyph(&term.line[y][x], usecurattr); } } void tdeletechar(int n) { - int dst, src, size; - Glyph *line; - - LIMIT(n, 0, term.col - term.c.x); + int src, dst, size; + Line line; + if (n <= 0) + return; dst = term.c.x; - src = term.c.x + n; + src = MIN(term.c.x + n, term.col); size = term.col - src; - line = term.line[term.c.y]; - - memmove(&line[dst], &line[src], size * sizeof(Glyph)); - tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); + if (size > 0) { /* otherwise src would point beyond the array + https://stackoverflow.com/questions/29844298 */ + line = term.line[term.c.y]; + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + } + tclearregion(dst + size, term.c.y, term.col - 1, term.c.y, 1); } void tinsertblank(int n) { - int dst, src, size; - Glyph *line; - - LIMIT(n, 0, term.col - term.c.x); + int src, dst, size; + Line line; - dst = term.c.x + n; + if (n <= 0) + return; + dst = MIN(term.c.x + n, term.col); src = term.c.x; size = term.col - dst; - line = term.line[term.c.y]; - - memmove(&line[dst], &line[src], size * sizeof(Glyph)); - tclearregion(src, term.c.y, dst - 1, term.c.y); + if (size > 0) { /* otherwise dst would point beyond the array */ + line = term.line[term.c.y]; + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + } + tclearregion(src, term.c.y, dst - 1, term.c.y, 1); } void tinsertblankline(int n) { if (BETWEEN(term.c.y, term.top, term.bot)) - tscrolldown(term.c.y, n, 0); + tscrolldown(term.c.y, n); } void tdeleteline(int n) { if (BETWEEN(term.c.y, term.top, term.bot)) - tscrollup(term.c.y, n, 0); + tscrollup(term.c.y, term.bot, n, SCROLL_NOSAVEHIST); } int32_t @@ -1528,7 +1695,7 @@ tsetscroll(int t, int b) void tsetmode(int priv, int set, const int *args, int narg) { - int alt; const int *lim; + const int *lim; for (lim = args + narg; args < lim; ++args) { if (priv) { @@ -1589,25 +1756,18 @@ tsetmode(int priv, int set, const int *args, int narg) xsetmode(set, MODE_8BIT); break; case 1049: /* swap screen & set/restore cursor as xterm */ - if (!allowaltscreen) - break; - tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); - /* FALLTHROUGH */ case 47: /* swap screen */ - case 1047: + case 1047: /* swap screen, clearing alternate screen */ if (!allowaltscreen) break; - alt = IS_SET(MODE_ALTSCREEN); - if (alt) { - tclearregion(0, 0, term.col-1, - term.row-1); - } - if (set ^ alt) /* set is always 1 or 0 */ - tswapscreen(); - if (*args != 1049) - break; - /* FALLTHROUGH */ + if (set) + tloadaltscreen(*args == 1049, *args == 1049); + else + tloaddefscreen(*args == 1047, *args == 1049); + break; case 1048: + if (!allowaltscreen) + break; tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); break; case 2004: /* 2004: bracketed paste mode */ @@ -1659,7 +1819,7 @@ void csihandle(void) { char buf[40]; - int len; + int n, x; switch (csiescseq.mode[0]) { default: @@ -1757,20 +1917,30 @@ csihandle(void) case 'J': /* ED -- Clear screen */ switch (csiescseq.arg[0]) { case 0: /* below */ - tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1); if (term.c.y < term.row-1) { - tclearregion(0, term.c.y+1, term.col-1, - term.row-1); + tclearregion(0, term.c.y+1, term.col-1, term.row-1, 1); } break; case 1: /* above */ - if (term.c.y > 1) - tclearregion(0, 0, term.col-1, term.c.y-1); - tclearregion(0, term.c.y, term.c.x, term.c.y); + if (term.c.y >= 1) + tclearregion(0, 0, term.col-1, term.c.y-1, 1); + tclearregion(0, term.c.y, term.c.x, term.c.y, 1); break; case 2: /* all */ - tclearregion(0, 0, term.col-1, term.row-1); - break; + if (IS_SET(MODE_ALTSCREEN)) { + tclearregion(0, 0, term.col-1, term.row-1, 1); + break; + } + /* vte does this: + tscrollup(0, term.row-1, term.row, SCROLL_SAVEHIST); */ + + /* alacritty does this: */ + for (n = term.row-1; n >= 0 && tlinelen(term.line[n]) == 0; n--); + if (n >= 0) + tscrollup(0, term.row-1, n+1, SCROLL_SAVEHIST); + tscrollup(0, term.row-1, term.row-n-1, SCROLL_NOSAVEHIST); + break; default: goto unknown; } @@ -1778,24 +1948,24 @@ csihandle(void) case 'K': /* EL -- Clear line */ switch (csiescseq.arg[0]) { case 0: /* right */ - tclearregion(term.c.x, term.c.y, term.col-1, - term.c.y); + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1); break; case 1: /* left */ - tclearregion(0, term.c.y, term.c.x, term.c.y); + tclearregion(0, term.c.y, term.c.x, term.c.y, 1); break; case 2: /* all */ - tclearregion(0, term.c.y, term.col-1, term.c.y); + tclearregion(0, term.c.y, term.col-1, term.c.y, 1); break; } break; case 'S': /* SU -- Scroll line up */ DEFAULT(csiescseq.arg[0], 1); - tscrollup(term.top, csiescseq.arg[0], 0); + /* xterm, urxvt, alacritty save this in history */ + tscrollup(term.top, term.bot, csiescseq.arg[0], SCROLL_SAVEHIST); break; case 'T': /* SD -- Scroll line down */ DEFAULT(csiescseq.arg[0], 1); - tscrolldown(term.top, csiescseq.arg[0], 0); + tscrolldown(term.top, csiescseq.arg[0]); break; case 'L': /* IL -- Insert blank lines */ DEFAULT(csiescseq.arg[0], 1); @@ -1809,9 +1979,11 @@ csihandle(void) tdeleteline(csiescseq.arg[0]); break; case 'X': /* ECH -- Erase char */ + if (csiescseq.arg[0] < 0) + return; DEFAULT(csiescseq.arg[0], 1); - tclearregion(term.c.x, term.c.y, - term.c.x + csiescseq.arg[0] - 1, term.c.y); + x = MIN(term.c.x + csiescseq.arg[0], term.col) - 1; + tclearregion(term.c.x, term.c.y, x, term.c.y, 1); break; case 'P': /* DCH -- Delete char */ DEFAULT(csiescseq.arg[0], 1); @@ -1833,9 +2005,9 @@ csihandle(void) break; case 'n': /* DSR – Device Status Report (cursor position) */ if (csiescseq.arg[0] == 6) { - len = snprintf(buf, sizeof(buf), "\033[%i;%iR", + n = snprintf(buf, sizeof(buf), "\033[%i;%iR", term.c.y+1, term.c.x+1); - ttywrite(buf, len, 0); + ttywrite(buf, n, 0); } break; case 'r': /* DECSTBM -- Set Scrolling Region */ @@ -2128,16 +2300,8 @@ tdumpsel(void) void tdumpline(int n) { - char buf[UTF_SIZ]; - const Glyph *bp, *end; - - bp = &term.line[n][0]; - end = &bp[MIN(tlinelen(n), term.col) - 1]; - if (bp != end || bp->u != ' ') { - for ( ; bp <= end; ++bp) - tprinter(buf, utf8encode(bp->u, buf)); - } - tprinter("\n", 1); + char str[(term.col + 1) * UTF_SIZ]; + tprinter(str, tgetline(str, &term.line[n][0])); } void @@ -2358,7 +2522,7 @@ eschandle(uchar ascii) return 0; case 'D': /* IND -- Linefeed */ if (term.c.y == term.bot) { - tscrollup(term.top, 1, 1); + tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST); } else { tmoveto(term.c.x, term.c.y+1); } @@ -2371,7 +2535,7 @@ eschandle(uchar ascii) break; case 'M': /* RI -- Reverse index */ if (term.c.y == term.top) { - tscrolldown(term.top, 1, 1); + tscrolldown(term.top, 1); } else { tmoveto(term.c.x, term.c.y-1); } @@ -2511,7 +2675,8 @@ check_control_code: */ return; } - if (selected(term.c.x, term.c.y)) + /* selected() takes relative coordinates */ + if (selected(term.c.x + term.scr, term.c.y + term.scr)) selclear(); gp = &term.line[term.c.y][term.c.x]; @@ -2546,6 +2711,7 @@ check_control_code: if (term.c.x+width < term.col) { tmoveto(term.c.x+width, term.c.y); } else { + term.wrapcwidth[IS_SET(MODE_ALTSCREEN)] = width; term.c.state |= CURSOR_WRAPNEXT; } } @@ -2583,93 +2749,275 @@ twrite(const char *buf, int buflen, int show_ctrl) } void -tresize(int col, int row) +treflow(int col, int row) { int i, j; - int minrow = MIN(row, term.row); - int mincol = MIN(col, term.col); - int *bp; - TCursor c; - - if (col < 1 || row < 1) { - fprintf(stderr, - "tresize: error resizing to %dx%d\n", col, row); - return; + int oce, nce, bot, scr; + int ox = 0, oy = -term.histf, nx = 0, ny = -1, len; + int cy = -1; /* proxy for new y coordinate of cursor */ + int nlines; + Line *buf, line; + + /* y coordinate of cursor line end */ + for (oce = term.c.y; oce < term.row - 1 && + tiswrapped(term.line[oce]); oce++); + + nlines = term.histf + oce + 1; + if (col < term.col) { + /* each line can take this many lines after reflow */ + j = (term.col + col - 1) / col; + nlines = j * nlines; + if (nlines > HISTSIZE + RESIZEBUFFER + row) { + nlines = HISTSIZE + RESIZEBUFFER + row; + oy = -(nlines / j - oce - 1); + } } + buf = xmalloc(nlines * sizeof(Line)); + do { + if (!nx) + buf[++ny] = xmalloc(col * sizeof(Glyph)); + if (!ox) { + line = TLINEABS(oy); + len = tlinelen(line); + } + if (oy == term.c.y) { + if (!ox) + len = MAX(len, term.c.x + 1); + /* update cursor */ + if (cy < 0 && term.c.x - ox < col - nx) { + term.c.x = nx + term.c.x - ox, cy = ny; + UPDATEWRAPNEXT(0, col); + } + } + /* get reflowed lines in buf */ + if (col - nx > len - ox) { + memcpy(&buf[ny][nx], &line[ox], (len-ox) * sizeof(Glyph)); + nx += len - ox; + if (len == 0 || !(line[len - 1].mode & ATTR_WRAP)) { + for (j = nx; j < col; j++) + tclearglyph(&buf[ny][j], 0); + nx = 0; + } else if (nx > 0) { + buf[ny][nx - 1].mode &= ~ATTR_WRAP; + } + ox = 0, oy++; + } else if (col - nx == len - ox) { + memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph)); + ox = 0, oy++, nx = 0; + } else/* if (col - nx < len - ox) */ { + memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph)); + ox += col - nx; + buf[ny][col - 1].mode |= ATTR_WRAP; + nx = 0; + } + } while (oy <= oce); + if (nx) + for (j = nx; j < col; j++) + tclearglyph(&buf[ny][j], 0); - /* - * slide screen to keep cursor where we expect it - - * tscrollup would work here, but we can optimize to - * memmove because we're freeing the earlier lines - */ - for (i = 0; i <= term.c.y - row; i++) { + /* free extra lines */ + for (i = row; i < term.row; i++) free(term.line[i]); - free(term.alt[i]); + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + + bot = MIN(ny, row - 1); + scr = MAX(row - term.row, 0); + /* update y coordinate of cursor line end */ + nce = MIN(oce + scr, bot); + /* update cursor y coordinate */ + term.c.y = nce - (ny - cy); + if (term.c.y < 0) { + j = nce, nce = MIN(nce + -term.c.y, bot); + term.c.y += nce - j; + while (term.c.y < 0) { + free(buf[ny--]); + term.c.y++; + } } - /* ensure that both src and dst are not NULL */ - if (i > 0) { - memmove(term.line, term.line + i, row * sizeof(Line)); - memmove(term.alt, term.alt + i, row * sizeof(Line)); + /* allocate new rows */ + for (i = row - 1; i > nce; i--) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + for (j = 0; j < col; j++) + tclearglyph(&term.line[i][j], 0); } - for (i += row; i < term.row; i++) { + /* fill visible area */ + for (/*i = nce */; i >= term.row; i--, ny--) + term.line[i] = buf[ny]; + for (/*i = term.row - 1 */; i >= 0; i--, ny--) { free(term.line[i]); - free(term.alt[i]); + term.line[i] = buf[ny]; + } + /* fill lines in history buffer and update term.histf */ + for (/*i = -1 */; ny >= 0 && i >= -HISTSIZE; i--, ny--) { + j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE; + free(term.hist[j]); + term.hist[j] = buf[ny]; + } + term.histf = -i - 1; + term.scr = MIN(term.scr, term.histf); + /* resize rest of the history lines */ + for (/*i = -term.histf - 1 */; i >= -HISTSIZE; i--) { + j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE; + term.hist[j] = xrealloc(term.hist[j], col * sizeof(Glyph)); } + free(buf); +} - /* resize to new height */ - term.line = xrealloc(term.line, row * sizeof(Line)); - term.alt = xrealloc(term.alt, row * sizeof(Line)); - term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); - term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); +void +rscrolldown(int n) +{ + int i; + Line temp; - for (i = 0; i < HISTSIZE; i++) { - term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); - for (j = mincol; j < col; j++) { - term.hist[i][j] = term.c.attr; - term.hist[i][j].u = ' '; - } - } + /* can never be true as of now + if (IS_SET(MODE_ALTSCREEN)) + return; */ - /* resize each row to new width, zero-pad if needed */ - for (i = 0; i < minrow; i++) { - term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); - term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); - } + if ((n = MIN(n, term.histf)) <= 0) + return; - /* allocate any new rows */ - for (/* i = minrow */; i < row; i++) { - term.line[i] = xmalloc(col * sizeof(Glyph)); - term.alt[i] = xmalloc(col * sizeof(Glyph)); + for (i = term.c.y + n; i >= n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; } + for (/*i = n - 1 */; i >= 0; i--) { + temp = term.line[i]; + term.line[i] = term.hist[term.histi]; + term.hist[term.histi] = temp; + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + } + term.c.y += n; + term.histf -= n; + if ((i = term.scr - n) >= 0) { + term.scr = i; + } else { + term.scr = 0; + if (sel.ob.x != -1 && !sel.alt) + selmove(-i); + } +} + +void +tresize(int col, int row) +{ + int *bp; + + /* col and row are always MAX(_, 1) + if (col < 1 || row < 1) { + fprintf(stderr, "tresize: error resizing to %dx%d\n", col, row); + return; + } */ + + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); if (col > term.col) { bp = term.tabs + term.col; - memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); while (--bp > term.tabs && !*bp) /* nothing */ ; for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) *bp = 1; } - /* update terminal size */ - term.col = col; - term.row = row; - /* reset scrolling region */ - tsetscroll(0, row-1); - /* make use of the LIMIT in tmoveto */ - tmoveto(term.c.x, term.c.y); - /* Clearing both screens (it makes dirty all lines) */ - c = term.c; - for (i = 0; i < 2; i++) { - if (mincol < col && 0 < minrow) { - tclearregion(mincol, 0, col - 1, minrow - 1); + + if (IS_SET(MODE_ALTSCREEN)) + tresizealt(col, row); + else + tresizedef(col, row); +} + +void +tresizedef(int col, int row) +{ + int i, j; + + /* return if dimensions haven't changed */ + if (term.col == col && term.row == row) { + tfulldirt(); + return; + } + if (col != term.col) { + if (!sel.alt) + selremove(); + treflow(col, row); + } else { + /* slide screen up if otherwise cursor would get out of the screen */ + if (term.c.y >= row) { + tscrollup(0, term.row - 1, term.c.y - row + 1, SCROLL_RESIZE); + term.c.y = row - 1; } - if (0 < col && minrow < row) { - tclearregion(0, minrow, col - 1, row - 1); + for (i = row; i < term.row; i++) + free(term.line[i]); + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + /* allocate any new rows */ + for (i = term.row; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + for (j = 0; j < col; j++) + tclearglyph(&term.line[i][j], 0); } - tswapscreen(); - tcursor(CURSOR_LOAD); + /* scroll down as much as height has increased */ + rscrolldown(row - term.row); + } + /* update terminal size */ + term.col = col, term.row = row; + /* reset scrolling region */ + term.top = 0, term.bot = row - 1; + /* dirty all lines */ + tfulldirt(); +} + +void +tresizealt(int col, int row) +{ + int i, j; + + /* return if dimensions haven't changed */ + if (term.col == col && term.row == row) { + tfulldirt(); + return; } - term.c = c; + if (sel.alt) + selremove(); + /* slide screen up if otherwise cursor would get out of the screen */ + for (i = 0; i <= term.c.y - row; i++) + free(term.line[i]); + if (i > 0) { + /* ensure that both src and dst are not NULL */ + memmove(term.line, term.line + i, row * sizeof(Line)); + term.c.y = row - 1; + } + for (i += row; i < term.row; i++) + free(term.line[i]); + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + /* resize to new width */ + for (i = 0; i < MIN(row, term.row); i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + for (j = term.col; j < col; j++) + tclearglyph(&term.line[i][j], 0); + } + /* allocate any new rows */ + for (/*i = MIN(row, term.row) */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + for (j = 0; j < col; j++) + tclearglyph(&term.line[i][j], 0); + } + /* update cursor */ + if (term.c.x >= col) { + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = col - 1; + } else { + UPDATEWRAPNEXT(1, col); + } + /* update terminal size */ + term.col = col, term.row = row; + /* reset scrolling region */ + term.top = 0, term.bot = row - 1; + /* dirty all lines */ + tfulldirt(); } void diff --git a/st.h b/st.h index 818a6f8..514ec08 100644 --- a/st.h +++ b/st.h @@ -22,17 +22,19 @@ enum glyph_attribute { ATTR_NULL = 0, - ATTR_BOLD = 1 << 0, - ATTR_FAINT = 1 << 1, - ATTR_ITALIC = 1 << 2, - ATTR_UNDERLINE = 1 << 3, - ATTR_BLINK = 1 << 4, - ATTR_REVERSE = 1 << 5, - ATTR_INVISIBLE = 1 << 6, - ATTR_STRUCK = 1 << 7, - ATTR_WRAP = 1 << 8, - ATTR_WIDE = 1 << 9, - ATTR_WDUMMY = 1 << 10, + ATTR_SET = 1 << 0, + ATTR_BOLD = 1 << 1, + ATTR_FAINT = 1 << 2, + ATTR_ITALIC = 1 << 3, + ATTR_UNDERLINE = 1 << 4, + ATTR_BLINK = 1 << 5, + ATTR_REVERSE = 1 << 6, + ATTR_INVISIBLE = 1 << 7, + ATTR_STRUCK = 1 << 8, + ATTR_WRAP = 1 << 9, + ATTR_WIDE = 1 << 10, + ATTR_WDUMMY = 1 << 11, + ATTR_SELECTED = 1 << 12, ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, }; @@ -90,6 +92,7 @@ void toggleprinter(const Arg *); int tattrset(int); void tnew(int, int); +int tisaltscreen(void); void tresize(int, int); void tsetdirtattr(int); void ttyhangup(void);