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); #if SIXEL_PATCH tdeleteimages(); #endif // SIXEL_PATCH } 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; kscrolldown(&((Arg){ .i = term.scr })); tswapscreen(); tresizealt(col, row); } if (clear) { tclearregion(0, 0, term.col-1, term.row-1, 1); #if SIXEL_PATCH tdeleteimages(); #endif // SIXEL_PATCH } } void selmove(int n) { sel.ob.y += n, sel.nb.y += n; sel.oe.y += n, sel.ne.y += n; } void tclearglyph(Glyph *gp, int usecurattr) { 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 SIXEL_PATCH void treflow_moveimages(int oldy, int newy) { ImageList *im; for (im = term.images; im; im = im->next) { if (im->y == oldy) im->reflow_y = newy; } } #endif // SIXEL_PATCH void treflow(int col, int row) { int i, j, x, x2; 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 buflen, nlines, del; Line *buf, bufline, line; #if SIXEL_PATCH ImageList *im, *next; for (im = term.images; im; im = im->next) im->reflow_y = INT_MIN; /* unset reflow_y */ #endif // SIXEL_PATCH /* y coordinate of cursor line end */ for (oce = term.c.y; oce < term.row - 1 && tiswrapped(term.line[oce]); oce++); nlines = HISTSIZE + row; buf = xmalloc(nlines * sizeof(Line)); do { if (!nx && ++ny < nlines) 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 */ bufline = buf[ny % nlines]; if (col - nx > len - ox) { memcpy(&bufline[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(&bufline[j], 0); #if SIXEL_PATCH treflow_moveimages(oy+term.scr, ny); #endif // SIXEL_PATCH nx = 0; } else if (nx > 0) { bufline[nx - 1].mode &= ~ATTR_WRAP; } ox = 0, oy++; } else if (col - nx == len - ox) { memcpy(&bufline[nx], &line[ox], (col-nx) * sizeof(Glyph)); #if SIXEL_PATCH treflow_moveimages(oy+term.scr, ny); #endif // SIXEL_PATCH ox = 0, oy++, nx = 0; } else/* if (col - nx < len - ox) */ { memcpy(&bufline[nx], &line[ox], (col-nx) * sizeof(Glyph)); if (bufline[col - 1].mode & ATTR_WIDE) { bufline[col - 2].mode |= ATTR_WRAP; tclearglyph(&bufline[col - 1], 0); ox--; } else { bufline[col - 1].mode |= ATTR_WRAP; } #if SIXEL_PATCH treflow_moveimages(oy+term.scr, ny); #endif // SIXEL_PATCH ox += col - nx; nx = 0; } } while (oy <= oce); if (nx) for (j = nx; j < col; j++) tclearglyph(&bufline[j], 0); /* free extra lines */ for (i = row; i < term.row; i++) free(term.line[i]); /* resize to new height */ term.line = xrealloc(term.line, row * sizeof(Line)); buflen = MIN(ny + 1, nlines); 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-- % nlines]); buflen--; term.c.y++; } } /* allocate new rows */ for (i = row - 1; i > nce; i--) { if (i >= term.row) term.line[i] = xmalloc(col * sizeof(Glyph)); else term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); for (j = 0; j < col; j++) tclearglyph(&term.line[i][j], 0); } /* fill visible area */ for (/*i = nce */; i >= term.row; i--, ny--, buflen--) term.line[i] = buf[ny % nlines]; for (/*i = term.row - 1 */; i >= 0; i--, ny--, buflen--) { free(term.line[i]); term.line[i] = buf[ny % nlines]; } /* fill lines in history buffer and update term.histf */ for (/*i = -1 */; buflen > 0 && i >= -HISTSIZE; i--, ny--, buflen--) { j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE; free(term.hist[j]); term.hist[j] = buf[ny % nlines]; } 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)); } #if SIXEL_PATCH /* move images to the final position */ for (im = term.images; im; im = next) { next = im->next; if (im->reflow_y == INT_MIN) { delete_image(im); } else { im->y = im->reflow_y - term.histf + term.scr - (ny + 1); if (im->y - term.scr < -HISTSIZE || im->y - term.scr >= row) delete_image(im); } } /* expand images into new text cells or * delete images if there is text behind them */ for (im = term.images; im; im = next) { next = im->next; if (im->x < col) { line = TLINE(im->y); x2 = MIN(im->x + im->cols, col); for (del = 0, x = im->x; x < x2; x++) { if ((del = line[x].mode & ATTR_SET)) break; line[x].u = ' '; line[x].mode = ATTR_SIXEL; } if (del) delete_image(im); } } #endif // SIXEL_PATCH for (; buflen > 0; ny--, buflen--) free(buf[ny % nlines]); free(buf); } void rscrolldown(int n) { int i; Line temp; /* can never be true as of now if (IS_SET(MODE_ALTSCREEN)) return; */ if ((n = MIN(n, term.histf)) <= 0) return; 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 { #if SIXEL_PATCH scroll_images(n - term.scr); #endif // SIXEL_PATCH term.scr = 0; if (sel.ob.x != -1 && !sel.alt) selmove(-i); } } 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; } 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); } /* 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; #if SIXEL_PATCH ImageList *im, *next; #endif // SIXEL_PATCH /* return if dimensions haven't changed */ if (term.col == col && term.row == row) { tfulldirt(); return; } 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)); #if SIXEL_PATCH scroll_images(-i); #endif // SIXEL_PATCH 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; #if SIXEL_PATCH /* delete or clip images if they are not inside the screen */ for (im = term.images; im; im = next) { next = im->next; if (im->x >= term.col || im->y >= term.row || im->y < 0) { delete_image(im); } else { if ((im->cols = MIN(im->x + im->cols, term.col) - im->x) <= 0) delete_image(im); } } #endif // SIXEL_PATCH /* dirty all lines */ tfulldirt(); } void kscrolldown(const Arg* a) { int n = a->i; if (!term.scr || IS_SET(MODE_ALTSCREEN)) return; if (n < 0) n = MAX(term.row / -n, 1); if (n <= term.scr) { term.scr -= n; } else { n = term.scr; term.scr = 0; } if (sel.ob.x != -1 && !sel.alt) selmove(-n); /* negate change in term.scr */ tfulldirt(); #if SIXEL_PATCH scroll_images(-1*n); #endif // SIXEL_PATCH #if OPENURLONCLICK_PATCH if (n > 0) restoremousecursor(); #endif // OPENURLONCLICK_PATCH } void kscrollup(const Arg* a) { int n = a->i; if (!term.histf || IS_SET(MODE_ALTSCREEN)) return; if (n < 0) n = MAX(term.row / -n, 1); if (term.scr + n <= term.histf) { term.scr += n; } 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(); #if SIXEL_PATCH scroll_images(n); #endif // SIXEL_PATCH #if OPENURLONCLICK_PATCH if (n > 0) restoremousecursor(); #endif // OPENURLONCLICK_PATCH } void tscrollup(int top, int bot, int n, int mode) { #if OPENURLONCLICK_PATCH restoremousecursor(); #endif //OPENURLONCLICK_PATCH int i, j, s; Line temp; int alt = IS_SET(MODE_ALTSCREEN); int savehist = !alt && top == 0 && mode != SCROLL_NOSAVEHIST; int scr = alt ? 0 : term.scr; #if SIXEL_PATCH int itop = top + scr, ibot = bot + scr; ImageList *im, *next; #endif // SIXEL_PATCH 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 + scr, bot + scr); } for (i = top; i <= bot-n; i++) { temp = term.line[i]; term.line[i] = term.line[i+n]; term.line[i+n] = temp; } #if SIXEL_PATCH if (alt || !savehist) { /* move images, if they are inside the scrolling region */ for (im = term.images; im; im = next) { next = im->next; if (im->y >= itop && im->y <= ibot) { im->y -= n; if (im->y < itop) delete_image(im); } } } else { /* move images, if they are inside the scrolling region or scrollback */ for (im = term.images; im; im = next) { next = im->next; im->y -= scr; if (im->y < 0) { im->y -= n; } else if (im->y >= top && im->y <= bot) { im->y -= n; if (im->y < top) im->y -= top; // move to scrollback } if (im->y < -HISTSIZE) delete_image(im); else im->y += term.scr; } } #endif // SIXEL_PATCH 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 tscrolldown(int top, int n) { #if OPENURLONCLICK_PATCH restoremousecursor(); #endif //OPENURLONCLICK_PATCH int i, bot = term.bot; int scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr; int itop = top + scr, ibot = bot + scr; Line temp; #if SIXEL_PATCH ImageList *im, *next; #endif // SIXEL_PATCH if (n <= 0) return; n = MIN(n, bot-top+1); tsetdirt(top + scr, bot + scr); tclearregion(0, bot-n+1, term.col-1, bot, 1); for (i = bot; i >= top+n; i--) { temp = term.line[i]; term.line[i] = term.line[i-n]; term.line[i-n] = temp; } #if SIXEL_PATCH /* move images, if they are inside the scrolling region */ for (im = term.images; im; im = next) { next = im->next; if (im->y >= itop && im->y <= ibot) { im->y += n; if (im->y > ibot) delete_image(im); } } #endif // SIXEL_PATCH if (sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN)) selscroll(top, bot, n); } void tresize(int col, int row) { int *bp; #if KEYBOARDSELECT_PATCH if (row != term.row || col != term.col) win.mode ^= kbds_keyboardhandler(XK_Escape, NULL, 0, 1); #endif // KEYBOARDSELECT_PATCH 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; } if (IS_SET(MODE_ALTSCREEN)) tresizealt(col, row); else tresizedef(col, row); } void tclearregion(int x1, int y1, int x2, int y2, int usecurattr) { int x, y; /* 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++) tclearglyph(&term.line[y][x], usecurattr); } } void tnew(int col, int row) { 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(); } void tdeletechar(int n) { int src, dst, size; Line line; if (n <= 0) return; dst = term.c.x; src = MIN(term.c.x + n, term.col); size = term.col - src; 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 src, dst, size; Line line; if (n <= 0) return; dst = MIN(term.c.x + n, term.col); src = term.c.x; size = term.col - dst; 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); } int tlinelen(Line line) { int i = term.col - 1; /* We are using a different algorithm on the alt screen because an * application might use spaces to clear the screen and in that case it is * impossible to find the end of the line when every cell has the ATTR_SET * attribute. The second algorithm is more accurate on the main screen and * and we can use it there. */ if (IS_SET(MODE_ALTSCREEN)) for (; i >= 0 && !(line[i].mode & ATTR_WRAP) && line[i].u == ' '; i--); else for (; i >= 0 && !(line[i].mode & (ATTR_SET | ATTR_WRAP)); i--); return i + 1; } int tiswrapped(Line line) { int len = tlinelen(line); return len > 0 && (line[len - 1].mode & ATTR_WRAP); } 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; } int regionselected(int x1, int y1, int x2, int y2) { 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; 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); } int selected(int x, int y) { return regionselected(x, y, x, y); } void selsnap(int *x, int *y, int direction) { int newx, newy; int rtop = 0, rbot = term.row - 1; int delim, prevdelim, maxlen; const Glyph *gp, *prevgp; if (!IS_SET(MODE_ALTSCREEN)) rtop += -term.histf + term.scr, rbot += term.scr; switch (sel.snap) { case SNAP_WORD: /* * Snap around if the word wraps around at the end or * beginning of a line. */ maxlen = (TLINE(*y)[term.col-2].mode & ATTR_WRAP) ? term.col-1 : term.col; LIMIT(*x, 0, maxlen - 1); prevgp = &TLINE(*y)[*x]; prevdelim = ISDELIM(prevgp->u); for (;;) { newx = *x + direction; newy = *y; if (!BETWEEN(newx, 0, maxlen - 1)) { newy += direction; if (!BETWEEN(newy, rtop, rbot)) break; if (!tiswrapped(TLINE(direction > 0 ? *y : newy))) break; maxlen = (TLINE(newy)[term.col-2].mode & ATTR_WRAP) ? term.col-1 : term.col; newx = direction > 0 ? 0 : maxlen - 1; } gp = &TLINE(newy)[newx]; delim = ISDELIM(gp->u); if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim || (delim && gp->u != prevgp->u))) break; *x = newx; *y = newy; if (!(gp->mode & ATTR_WDUMMY)) { prevgp = gp; prevdelim = delim; } } break; case SNAP_LINE: /* * Snap around if the the previous line or the current one * has set ATTR_WRAP at its end. Then the whole next or * previous line will be selected. */ *x = (direction < 0) ? 0 : term.col - 1; if (direction < 0) { for (; *y > rtop; *y -= 1) { if (!tiswrapped(TLINE(*y-1))) break; } } else if (direction > 0) { for (; *y < rbot; *y += 1) { if (!tiswrapped(TLINE(*y))) break; } } break; } } void selscroll(int top, int bot, int n) { /* turn absolute coordinates into relative */ top += term.scr, bot += term.scr; if (BETWEEN(sel.nb.y, top, bot) != BETWEEN(sel.ne.y, top, bot)) { selclear(); } else if (BETWEEN(sel.nb.y, top, bot)) { selmove(n); if (sel.nb.y < top || sel.ne.y > bot) selclear(); } } void tswapscreen(void) { static Line *altline; static int altcol, altrow; Line *tmpline = term.line; int tmpcol = term.col, tmprow = term.row; #if SIXEL_PATCH ImageList *im = term.images; #endif // SIXEL_PATCH term.line = altline; term.col = altcol, term.row = altrow; altline = tmpline; altcol = tmpcol, altrow = tmprow; term.mode ^= MODE_ALTSCREEN; #if SIXEL_PATCH term.images = term.images_alt; term.images_alt = im; #endif // SIXEL_PATCH } char * getsel(void) { char *str, *ptr; int y, lastx, linelen; const Glyph *gp, *lgp; if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) return NULL; 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++) { Line line = TLINE(y); if ((linelen = tlinelen(line)) == 0) { *ptr++ = '\n'; continue; } if (sel.type == SEL_RECTANGULAR) { gp = &line[sel.nb.x]; lastx = sel.ne.x; } else { gp = &line[sel.nb.y == y ? sel.nb.x : 0]; lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; } 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. * The best solution seems like to produce '\n' when * something is copied from st and convert '\n' to * '\r', when something to be pasted is received by * st. * FIXME: Fix the computer world. */ if ((y < sel.ne.y || lastx >= linelen) && (!(lgp->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) *ptr++ = '\n'; } *ptr = '\0'; return str; } void tdumpline(int n) { char str[(term.col + 1) * UTF_SIZ]; tprinter(str, tgetline(str, &term.line[n][0])); }