diff --git a/st.c b/st.c index 62def59..30df1c3 100644 --- a/st.c +++ b/st.c @@ -43,6 +43,10 @@ #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) #define ISDELIM(u) (u && wcschr(worddelimiters, u)) +#define TSCREEN term.screen[IS_SET(MODE_ALTSCREEN)] +#define TLINEOFFSET(y) (((y) + TSCREEN.cur - TSCREEN.off + TSCREEN.size) % TSCREEN.size) +#define TLINE(y) (TSCREEN.buffer[TLINEOFFSET(y)]) + enum term_mode { MODE_WRAP = 1 << 0, MODE_INSERT = 1 << 1, @@ -109,12 +113,21 @@ typedef struct { int alt; } Selection; +/* Screen lines */ +typedef struct { + Line* buffer; /* ring buffer */ + int size; /* size of buffer */ + int cur; /* start of active screen */ + int off; /* scrollback line offset */ + TCursor sc; /* saved cursor */ +} LineBuffer; + /* Internal representation of the screen */ typedef struct { int row; /* nb row */ int col; /* nb col */ - Line *line; /* screen */ - Line *alt; /* alternate screen */ + LineBuffer screen[2]; /* screen and alternate screen */ + int linelen; /* allocated line length */ int *dirty; /* dirtyness of lines */ TCursor c; /* cursor */ int ocx; /* old cursor col */ @@ -203,6 +216,8 @@ static void tdeftran(char); static void tstrsequence(uchar); static void drawregion(int, int, int, int); +static void clearline(Line, Glyph, int, int); +static Line ensureline(Line); static void selnormalize(void); static void selscroll(int, int); @@ -408,11 +423,12 @@ int tlinelen(int y) { int i = term.col; + Line line = TLINE(y); - if (term.line[y][i - 1].mode & ATTR_WRAP) + if (line[i - 1].mode & ATTR_WRAP) return i; - while (i > 0 && term.line[y][i - 1].u == ' ') + while (i > 0 && line[i - 1].u == ' ') --i; return i; @@ -521,7 +537,7 @@ selsnap(int *x, int *y, int direction) * Snap around if the word wraps around at the end or * beginning of a line. */ - prevgp = &term.line[*y][*x]; + prevgp = &TLINE(*y)[*x]; prevdelim = ISDELIM(prevgp->u); for (;;) { newx = *x + direction; @@ -536,14 +552,14 @@ selsnap(int *x, int *y, int direction) yt = *y, xt = *x; else yt = newy, xt = newx; - if (!(term.line[yt][xt].mode & ATTR_WRAP)) + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) break; } if (newx >= tlinelen(newy)) break; - gp = &term.line[newy][newx]; + gp = &TLINE(newy)[newx]; delim = ISDELIM(gp->u); if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim || (delim && gp->u != prevgp->u))) @@ -564,14 +580,14 @@ selsnap(int *x, int *y, int direction) *x = (direction < 0) ? 0 : term.col - 1; if (direction < 0) { for (; *y > 0; *y += direction) { - if (!(term.line[*y-1][term.col-1].mode + if (!(TLINE(*y-1)[term.col-1].mode & ATTR_WRAP)) { break; } } } else if (direction > 0) { for (; *y < term.row-1; *y += direction) { - if (!(term.line[*y][term.col-1].mode + if (!(TLINE(*y)[term.col-1].mode & ATTR_WRAP)) { break; } @@ -602,13 +618,13 @@ getsel(void) } if (sel.type == SEL_RECTANGULAR) { - gp = &term.line[y][sel.nb.x]; + gp = &TLINE(y)[sel.nb.x]; lastx = sel.ne.x; } else { - gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; + gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; } - last = &term.line[y][MIN(lastx, linelen-1)]; + last = &TLINE(y)[MIN(lastx, linelen-1)]; while (last >= gp && last->u == ' ') --last; @@ -949,12 +965,15 @@ int tattrset(int attr) { int i, j; + int y = TLINEOFFSET(0); for (i = 0; i < term.row-1; i++) { + Line line = TSCREEN.buffer[y]; for (j = 0; j < term.col-1; j++) { - if (term.line[i][j].mode & attr) + if (line[j].mode & attr) return 1; } + y = (y+1) % TSCREEN.size; } return 0; @@ -976,14 +995,17 @@ void tsetdirtattr(int attr) { int i, j; + int y = TLINEOFFSET(0); for (i = 0; i < term.row-1; i++) { + Line line = TSCREEN.buffer[y]; for (j = 0; j < term.col-1; j++) { - if (term.line[i][j].mode & attr) { + if (line[j].mode & attr) { tsetdirt(i, i); break; } } + y = (y+1) % TSCREEN.size; } } @@ -996,27 +1018,19 @@ tfulldirt(void) void tcursor(int mode) { - static TCursor c[2]; - int alt = IS_SET(MODE_ALTSCREEN); - if (mode == CURSOR_SAVE) { - c[alt] = term.c; + TSCREEN.sc = term.c; } else if (mode == CURSOR_LOAD) { - term.c = c[alt]; - tmoveto(c[alt].x, c[alt].y); + term.c = TSCREEN.sc; + tmoveto(term.c.x, term.c.y); } } void treset(void) { - uint i; - - term.c = (TCursor){{ - .mode = ATTR_NULL, - .fg = defaultfg, - .bg = defaultbg - }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + int i, j; + Glyph g = (Glyph){ .fg = defaultfg, .bg = defaultbg}; memset(term.tabs, 0, term.col * sizeof(*term.tabs)); for (i = tabspaces; i < term.col; i += tabspaces) @@ -1028,32 +1042,85 @@ treset(void) term.charset = 0; for (i = 0; i < 2; i++) { - tmoveto(0, 0); - tcursor(CURSOR_SAVE); - tclearregion(0, 0, term.col-1, term.row-1); - tswapscreen(); + term.screen[i].sc = (TCursor){{ + .fg = defaultfg, + .bg = defaultbg + }}; + term.screen[i].cur = 0; + term.screen[i].off = 0; + for (j = 0; j < term.row; ++j) { + if (term.col != term.linelen) + term.screen[i].buffer[j] = xrealloc(term.screen[i].buffer[j], term.col * sizeof(Glyph)); + clearline(term.screen[i].buffer[j], g, 0, term.col); + } + for (j = term.row; j < term.screen[i].size; ++j) { + free(term.screen[i].buffer[j]); + term.screen[i].buffer[j] = NULL; + } } + tcursor(CURSOR_LOAD); + term.linelen = term.col; + tfulldirt(); } void tnew(int col, int row) { - term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + int i; + term = (Term){}; + term.screen[0].buffer = xmalloc(HISTSIZE * sizeof(Line)); + term.screen[0].size = HISTSIZE; + term.screen[1].buffer = NULL; + for (i = 0; i < HISTSIZE; ++i) term.screen[0].buffer[i] = NULL; + tresize(col, row); treset(); } +int tisaltscr(void) +{ + return IS_SET(MODE_ALTSCREEN); +} + void tswapscreen(void) { - Line *tmp = term.line; - - term.line = term.alt; - term.alt = tmp; term.mode ^= MODE_ALTSCREEN; tfulldirt(); } +void +kscrollup(const Arg *a) +{ + int n = a->i; + + if (IS_SET(MODE_ALTSCREEN)) + return; + + if (n < 0) n = (-n) * term.row; + if (n > TSCREEN.size - term.row - TSCREEN.off) n = TSCREEN.size - term.row - TSCREEN.off; + while (!TLINE(-n)) --n; + TSCREEN.off += n; + selscroll(0, n); + tfulldirt(); +} + +void +kscrolldown(const Arg *a) +{ + + int n = a->i; + + if (IS_SET(MODE_ALTSCREEN)) + return; + + if (n < 0) n = (-n) * term.row; + if (n > TSCREEN.off) n = TSCREEN.off; + TSCREEN.off -= n; + selscroll(0, -n); + tfulldirt(); +} + void tscrolldown(int orig, int n) { @@ -1062,15 +1129,29 @@ tscrolldown(int orig, int n) LIMIT(n, 0, term.bot-orig+1); - tsetdirt(orig, term.bot-n); - tclearregion(0, term.bot-n+1, term.col-1, term.bot); - - for (i = term.bot; i >= orig+n; i--) { - temp = term.line[i]; - term.line[i] = term.line[i-n]; - term.line[i-n] = temp; + /* Ensure that lines are allocated */ + for (i = -n; i < 0; i++) { + TLINE(i) = ensureline(TLINE(i)); } + /* Shift non-scrolling areas in ring buffer */ + for (i = term.bot+1; i < term.row; i++) { + temp = TLINE(i); + TLINE(i) = TLINE(i-n); + TLINE(i-n) = temp; + } + for (i = 0; i < orig; i++) { + temp = TLINE(i); + TLINE(i) = TLINE(i-n); + TLINE(i-n) = temp; + } + + /* Scroll buffer */ + TSCREEN.cur = (TSCREEN.cur + TSCREEN.size - n) % TSCREEN.size; + /* Clear lines that have entered the view */ + tclearregion(0, orig, term.linelen-1, orig+n-1); + /* Redraw portion of the screen that has scrolled */ + tsetdirt(orig+n-1, term.bot); selscroll(orig, n); } @@ -1082,15 +1163,29 @@ tscrollup(int orig, int n) LIMIT(n, 0, term.bot-orig+1); - tclearregion(0, orig, term.col-1, orig+n-1); - tsetdirt(orig+n, term.bot); - - for (i = orig; i <= term.bot-n; i++) { - temp = term.line[i]; - term.line[i] = term.line[i+n]; - term.line[i+n] = temp; + /* Ensure that lines are allocated */ + for (i = term.row; i < term.row + n; i++) { + TLINE(i) = ensureline(TLINE(i)); } + /* Shift non-scrolling areas in ring buffer */ + for (i = orig-1; i >= 0; i--) { + temp = TLINE(i); + TLINE(i) = TLINE(i+n); + TLINE(i+n) = temp; + } + for (i = term.row-1; i >term.bot; i--) { + temp = TLINE(i); + TLINE(i) = TLINE(i+n); + TLINE(i+n) = temp; + } + + /* Scroll buffer */ + TSCREEN.cur = (TSCREEN.cur + n) % TSCREEN.size; + /* Clear lines that have entered the view */ + tclearregion(0, term.bot-n+1, term.linelen-1, term.bot); + /* Redraw portion of the screen that has scrolled */ + tsetdirt(orig, term.bot-n+1); selscroll(orig, -n); } @@ -1194,6 +1289,7 @@ tsetchar(Rune u, const Glyph *attr, int x, int y) "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ }; + Line line = TLINE(y); /* * The table is proudly stolen from rxvt. @@ -1202,25 +1298,25 @@ tsetchar(Rune u, const Glyph *attr, int x, int y) BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); - if (term.line[y][x].mode & ATTR_WIDE) { + if (line[x].mode & ATTR_WIDE) { if (x+1 < term.col) { - term.line[y][x+1].u = ' '; - term.line[y][x+1].mode &= ~ATTR_WDUMMY; + line[x+1].u = ' '; + line[x+1].mode &= ~ATTR_WDUMMY; } - } else if (term.line[y][x].mode & ATTR_WDUMMY) { - term.line[y][x-1].u = ' '; - term.line[y][x-1].mode &= ~ATTR_WIDE; + } else if (line[x].mode & ATTR_WDUMMY) { + line[x-1].u = ' '; + line[x-1].mode &= ~ATTR_WIDE; } term.dirty[y] = 1; - term.line[y][x] = *attr; - term.line[y][x].u = u; + line[x] = *attr; + line[x].u = u; } void tclearregion(int x1, int y1, int x2, int y2) { - int x, y, temp; + int x, y, L, S, temp; Glyph *gp; if (x1 > x2) @@ -1228,15 +1324,16 @@ tclearregion(int x1, int y1, int x2, int y2) if (y1 > y2) temp = y1, y1 = y2, y2 = temp; - LIMIT(x1, 0, term.col-1); - LIMIT(x2, 0, term.col-1); + LIMIT(x1, 0, term.linelen-1); + LIMIT(x2, 0, term.linelen-1); LIMIT(y1, 0, term.row-1); LIMIT(y2, 0, term.row-1); + L = TLINEOFFSET(y1); for (y = y1; y <= y2; y++) { term.dirty[y] = 1; for (x = x1; x <= x2; x++) { - gp = &term.line[y][x]; + gp = &TSCREEN.buffer[L][x]; if (selected(x, y)) selclear(); gp->fg = term.c.attr.fg; @@ -1244,6 +1341,7 @@ tclearregion(int x1, int y1, int x2, int y2) gp->mode = 0; gp->u = ' '; } + L = (L + 1) % TSCREEN.size; } } @@ -1258,7 +1356,7 @@ tdeletechar(int n) dst = term.c.x; src = term.c.x + n; size = term.col - src; - line = term.line[term.c.y]; + line = TLINE(term.c.y); memmove(&line[dst], &line[src], size * sizeof(Glyph)); tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); @@ -1275,7 +1373,7 @@ tinsertblank(int n) dst = term.c.x + n; src = term.c.x; size = term.col - dst; - line = term.line[term.c.y]; + line = TLINE(term.c.y); memmove(&line[dst], &line[src], size * sizeof(Glyph)); tclearregion(src, term.c.y, dst - 1, term.c.y); @@ -2069,7 +2167,7 @@ tdumpline(int n) char buf[UTF_SIZ]; const Glyph *bp, *end; - bp = &term.line[n][0]; + bp = &TLINE(n)[0]; end = &bp[MIN(tlinelen(n), term.col) - 1]; if (bp != end || bp->u != ' ') { for ( ; bp <= end; ++bp) @@ -2452,11 +2550,11 @@ check_control_code: if (selected(term.c.x, term.c.y)) selclear(); - gp = &term.line[term.c.y][term.c.x]; + gp = &TLINE(term.c.y)[term.c.x]; if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { gp->mode |= ATTR_WRAP; tnewline(1); - gp = &term.line[term.c.y][term.c.x]; + gp = &TLINE(term.c.y)[term.c.x]; } if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) @@ -2464,7 +2562,7 @@ check_control_code: if (term.c.x+width > term.col) { tnewline(1); - gp = &term.line[term.c.y][term.c.x]; + gp = &TLINE(term.c.y)[term.c.x]; } tsetchar(u, &term.c.attr, term.c.x, term.c.y); @@ -2495,6 +2593,11 @@ twrite(const char *buf, int buflen, int show_ctrl) Rune u; int n; + if (TSCREEN.off) { + TSCREEN.off = 0; + tfulldirt(); + } + for (n = 0; n < buflen; n += charsize) { if (IS_SET(MODE_UTF8)) { /* process a complete utf8 char */ @@ -2521,56 +2624,85 @@ twrite(const char *buf, int buflen, int show_ctrl) } void -tresize(int col, int row) +clearline(Line line, Glyph g, int x, int xend) { int i; + g.mode = 0; + g.u = ' '; + for (i = x; i < xend; ++i) { + line[i] = g; + } +} + +Line +ensureline(Line line) +{ + if (!line) { + line = xmalloc(term.linelen * sizeof(Glyph)); + } + return line; +} + +void +tresize(int col, int row) +{ + int i, j; int minrow = MIN(row, term.row); int mincol = MIN(col, term.col); + int linelen = MAX(col, term.linelen); int *bp; - TCursor c; - if (col < 1 || row < 1) { + if (col < 1 || row < 1 || row > HISTSIZE) { fprintf(stderr, "tresize: error resizing to %dx%d\n", col, row); return; } - /* - * 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(term.line[i]); - free(term.alt[i]); + /* Shift buffer to keep the cursor where we expect it */ + if (row <= term.c.y) { + term.screen[0].cur = (term.screen[0].cur - row + term.c.y + 1) % term.screen[0].size; } - /* 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)); + + /* Resize and clear line buffers as needed */ + if (linelen > term.linelen) { + for (i = 0; i < term.screen[0].size; ++i) { + if (term.screen[0].buffer[i]) { + term.screen[0].buffer[i] = xrealloc(term.screen[0].buffer[i], linelen * sizeof(Glyph)); + clearline(term.screen[0].buffer[i], term.c.attr, term.linelen, linelen); + } + } + for (i = 0; i < minrow; ++i) { + term.screen[1].buffer[i] = xrealloc(term.screen[1].buffer[i], linelen * sizeof(Glyph)); + clearline(term.screen[1].buffer[i], term.c.attr, term.linelen, linelen); + } } - for (i += row; i < term.row; i++) { - free(term.line[i]); - free(term.alt[i]); + /* Allocate all visible lines for regular line buffer */ + for (j = term.screen[0].cur, i = 0; i < row; ++i, j = (j + 1) % term.screen[0].size) + { + if (!term.screen[0].buffer[j]) { + term.screen[0].buffer[j] = xmalloc(linelen * sizeof(Glyph)); + } + if (i >= term.row) { + clearline(term.screen[0].buffer[j], term.c.attr, 0, linelen); + } + } + /* Resize alt screen */ + term.screen[1].cur = 0; + term.screen[1].size = row; + for (i = row; i < term.row; ++i) { + free(term.screen[1].buffer[i]); + } + term.screen[1].buffer = xrealloc(term.screen[1].buffer, row * sizeof(Line)); + for (i = term.row; i < row; ++i) { + term.screen[1].buffer[i] = xmalloc(linelen * sizeof(Glyph)); + clearline(term.screen[1].buffer[i], term.c.attr, 0, linelen); } /* 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)); - /* 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)); - } - - /* allocate any new rows */ - for (/* i = minrow */; i < row; i++) { - term.line[i] = xmalloc(col * sizeof(Glyph)); - term.alt[i] = xmalloc(col * sizeof(Glyph)); - } + /* fix tabstops */ if (col > term.col) { bp = term.tabs + term.col; @@ -2580,26 +2712,16 @@ tresize(int col, int row) for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) *bp = 1; } + /* update terminal size */ term.col = col; term.row = row; + term.linelen = linelen; /* 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 (0 < col && minrow < row) { - tclearregion(0, minrow, col - 1, row - 1); - } - tswapscreen(); - tcursor(CURSOR_LOAD); - } - term.c = c; + tfulldirt(); } void @@ -2611,14 +2733,15 @@ resettitle(void) void drawregion(int x1, int y1, int x2, int y2) { - int y; + int y, L; + L = TLINEOFFSET(y1); for (y = y1; y < y2; y++) { - if (!term.dirty[y]) - continue; - - term.dirty[y] = 0; - xdrawline(term.line[y], x1, y, x2); + if (term.dirty[y]) { + term.dirty[y] = 0; + xdrawline(TSCREEN.buffer[L], x1, y, x2); + } + L = (L + 1) % TSCREEN.size; } } @@ -2633,14 +2756,15 @@ draw(void) /* adjust cursor position */ LIMIT(term.ocx, 0, term.col-1); LIMIT(term.ocy, 0, term.row-1); - if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + if (TLINE(term.ocy)[term.ocx].mode & ATTR_WDUMMY) term.ocx--; - if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + if (TLINE(term.c.y)[cx].mode & ATTR_WDUMMY) cx--; drawregion(0, 0, term.col, term.row); - xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], - term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + if (TSCREEN.off == 0) + xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx], + term.ocx, term.ocy, TLINE(term.ocy)[term.ocx]); term.ocx = cx; term.ocy = term.c.y; xfinishdraw(); diff --git a/st.h b/st.h index fd3b0d8..952cb8e 100644 --- a/st.h +++ b/st.h @@ -19,6 +19,7 @@ #define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) #define IS_TRUECOL(x) (1 << 24 & (x)) +#define HISTSIZE 2000 enum glyph_attribute { ATTR_NULL = 0, @@ -87,6 +88,7 @@ void sendbreak(const Arg *); void toggleprinter(const Arg *); int tattrset(int); +int tisaltscr(void); void tnew(int, int); void tresize(int, int); void tsetdirtattr(int); diff --git a/x.c b/x.c index aa09997..c5bf771 100644 --- a/x.c +++ b/x.c @@ -34,6 +34,7 @@ typedef struct { void (*func)(const Arg *); const Arg arg; uint release; + int altscrn; /* 0: don't care, -1: not alt screen, 1: alt screen */ } MouseShortcut; typedef struct { @@ -59,6 +60,8 @@ static void zoom(const Arg *); static void zoomabs(const Arg *); static void zoomreset(const Arg *); static void ttysend(const Arg *); +void kscrollup(const Arg *); +void kscrolldown(const Arg *); /* config.h for applying patches and the configuration. */ #include "config.h" @@ -455,6 +458,7 @@ mouseaction(XEvent *e, uint release) for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { if (ms->release == release && ms->button == e->xbutton.button && + (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) && (match(ms->mod, state) || /* exact or forced */ match(ms->mod, state & ~forcemousemod))) { ms->func(&(ms->arg));