#include <limits.h>
#include <pwd.h>
#include <stdarg.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void tdeleteline(int);
static void tinsertblank(int);
static void tinsertblankline(int);
-static int tlinelen(int);
+static int tlinelen(Line);
static void tmoveto(int, int);
static void tmoveato(int, int);
static void tnewline(int);
static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
+typedef struct
+{
+ Line *buf; /* ring of Line pointers */
+ int cap; /* max number of lines */
+ int len; /* current number of valid lines (<= cap) */
+ int head; /* physical index of logical oldest (valid when len>0) */
+ uint64_t base; /* Can overflow in the extreme */
+ /*
+ * max_width tracks the widest line ever pushed to scrollback.
+ * It may be conservative (stale) if that line has since been
+ * evicted from the ring buffer, which is acceptable - it just
+ * means we might reflow when not strictly necessary, which is
+ * better than skipping a needed reflow.
+ */
+ int max_width;
+ int view_offset; /* 0 means live screen */
+} Scrollback;
+
+static Scrollback sb;
+
+static int
+sb_phys_index(int logical_idx)
+{
+ /* logical_idx: 0..sb.len-1 (0 = oldest) */
+ return (sb.head + logical_idx) % sb.cap;
+}
+
+static Line
+lineclone(Line src)
+{
+ Line dst;
+
+ if (!src)
+ return NULL;
+
+ dst = xmalloc(term.col * sizeof(Glyph));
+ memcpy(dst, src, term.col * sizeof(Glyph));
+ return dst;
+}
+
+static void
+sb_init(int lines)
+{
+ int i;
+
+ sb.buf = xmalloc(sizeof(Line) * lines);
+ sb.cap = lines;
+ sb.len = 0;
+ sb.head = 0;
+ sb.base = 0;
+ for (i = 0; i < sb.cap; i++)
+ sb.buf[i] = NULL;
+
+ sb.view_offset = 0;
+ sb.max_width = 0;
+}
+
+/* Push one screen line into scrollback.
+ * Overwrites oldest when full (ring buffer).
+ */
+static void
+sb_push(Line line)
+{
+ Line copy;
+ int tail;
+ int width;
+
+ if (sb.cap <= 0)
+ return;
+
+ copy = lineclone(line);
+
+ if (sb.len < sb.cap) {
+ tail = sb_phys_index(sb.len);
+ sb.buf[tail] = copy;
+ sb.len++;
+ } else {
+ /* We might've just evicted the widest line... */
+ free(sb.buf[sb.head]);
+ sb.buf[sb.head] = copy;
+ sb.head = (sb.head + 1) % sb.cap;
+ sb.base++;
+ }
+ width = tlinelen(copy);
+ /* ...so max_width might be stale. */
+ if (width > sb.max_width)
+ sb.max_width = width;
+}
+
+static Line
+sb_get(int idx)
+{
+ /* idx is logical: 0..sb.len-1 */
+ if (idx < 0 || idx >= sb.len)
+ return NULL;
+ return sb.buf[sb_phys_index(idx)];
+}
+
+static void
+sb_clear(void)
+{
+ int i;
+ int p;
+
+ if (!sb.buf)
+ return;
+
+ for (i = 0; i < sb.len; i++) {
+ p = sb_phys_index(i);
+ if (sb.buf[p]) {
+ free(sb.buf[p]);
+ sb.buf[p] = NULL;
+ }
+ }
+
+ sb.len = 0;
+ sb.head = 0;
+ sb.base = 0;
+ sb.view_offset = 0;
+ sb.max_width = 0;
+}
+
+/*
+ * Reflows the scrollback buffer to fit a new terminal width.
+ *
+ * The algorithm works in three steps:
+ * 1) Unwrap: It iterates through the existing history, joining physical lines
+ * marked with ATTR_WRAP into a single continuous 'logical' line.
+ * 2) Reflow: It slices this logical line into new chunks of size 'col'.
+ * - New wrap flags are applied where the text exceeds the new width.
+ * - Trailing spaces are trimmed to prevent ghost padding.
+ * 3) Rebuild: The new lines are pushed into a fresh ring buffer.
+ * - Uses O(1) ring insertion (updating head/tail) to avoid expensive
+ * memmoves during resize, but it is still O(N) where N is the existing
+ * history.
+ *
+ * Note: During reflow we reset sb to match the rebuilt buffer
+ * (head, base and len might change).
+ */
+static void
+sb_resize(int col)
+{
+ Line *new_buf;
+ int i, j;
+ int new_len, logical_cap, logical_len, is_wrapped, cursor;
+ int copy_width, tail, current_width;
+ Line logical, line, nl;
+ uint64_t new_base = 0;
+ int new_head = 0;
+ int new_max_width = 0;
+ Glyph *g;
+
+ new_len = 0;
+
+ if (sb.len == 0)
+ return;
+
+ new_buf = xmalloc(sizeof(Line) * sb.cap);
+ for (i = 0; i < sb.cap; i++)
+ new_buf[i] = NULL;
+
+ logical_cap = term.col * 2;
+ logical = xmalloc(logical_cap * sizeof(Glyph));
+ logical_len = 0;
+
+ for (i = 0; i < sb.len; i++) {
+ /* Unwrap: Accumulate physical lines into one logical line. */
+ line = sb_get(i);
+ is_wrapped = (line[term.col - 1].mode & ATTR_WRAP);
+ if (logical_len + term.col > logical_cap) {
+ logical_cap *= 2;
+ logical = xrealloc(logical, logical_cap * sizeof(Glyph));
+ }
+
+ memcpy(logical + logical_len, line, term.col * sizeof(Glyph));
+ for (j = 0; j < term.col; j++) {
+ logical[logical_len + j].mode &= ~ATTR_WRAP;
+ }
+ logical_len += term.col;
+ /* If the line was wrapped, continue accumulating before reflowing. */
+ if (is_wrapped) {
+ continue;
+ }
+ /* Trim trailing spaces from the fully unwrapped line. */
+ while (logical_len > 0) {
+ g = &logical[logical_len - 1];
+ if (g->u == ' ' && g->bg == defaultbg
+ && (g->mode & ATTR_BOLD) == 0) {
+ logical_len--;
+ } else {
+ break;
+ }
+ }
+ if (logical_len == 0)
+ logical_len = 1;
+
+ /* Reflow: Split the logical line into new chunks. */
+ cursor = 0;
+ while (cursor < logical_len) {
+ nl = xmalloc(col * sizeof(Glyph));
+ for (j = 0; j < col; j++) {
+ nl[j].fg = defaultfg;
+ nl[j].bg = defaultbg;
+ nl[j].mode = 0;
+ nl[j].u = ' ';
+ }
+
+ copy_width = logical_len - cursor;
+ if (copy_width > col)
+ copy_width = col;
+
+ memcpy(nl, logical + cursor, copy_width * sizeof(Glyph));
+
+ for (j = 0; j < copy_width; j++) {
+ nl[j].mode &= ~ATTR_WRAP;
+ }
+
+ if (cursor + copy_width < logical_len) {
+ nl[col - 1].mode |= ATTR_WRAP;
+ } else {
+ nl[col - 1].mode &= ~ATTR_WRAP;
+ }
+
+ /* Rebuild: Push new lines into the ring buffer. */
+ if (new_len < sb.cap) {
+ tail = (new_head + new_len) % sb.cap;
+ new_buf[tail] = nl;
+ new_len++;
+ } else {
+ free(new_buf[new_head]);
+ new_buf[new_head] = nl;
+ new_head = (new_head + 1) % sb.cap;
+ new_base++;
+ }
+ current_width = (cursor + copy_width < logical_len) ? col : copy_width;
+ if (current_width > new_max_width)
+ new_max_width = current_width;
+ cursor += copy_width;
+ }
+ logical_len = 0;
+ }
+ free(logical);
+ sb_clear();
+ free(sb.buf);
+ sb.buf = new_buf;
+ sb.len = new_len;
+ sb.head = new_head;
+ sb.base = new_base;
+ sb.view_offset = 0;
+ sb.max_width = new_max_width;
+}
+
+static void
+sb_pop_screen(int loaded, int new_cols)
+{
+ int i, p;
+ int start_logical;
+ Line line;
+
+ loaded = MIN(loaded, sb.len);
+ start_logical = sb.len - loaded;
+ new_cols = MIN(new_cols, term.col);
+ for (i = 0; i < loaded; i++) {
+ p = sb_phys_index(start_logical + i);
+ line = sb.buf[p];
+
+ memcpy(term.line[i], line, new_cols * sizeof(Glyph));
+
+ free(line);
+ sb.buf[p] = NULL;
+ }
+
+ sb.len -= loaded;
+}
+
+static uint64_t
+sb_view_start(void)
+{
+ return sb.base + sb.len - sb.view_offset;
+}
+
+static void
+sb_view_changed(void)
+{
+ if (!term.dirty || term.row <= 0)
+ return;
+ tfulldirt();
+}
+
+static void
+selscrollback(int delta)
+{
+ if (delta == 0)
+ return;
+
+ if (sel.ob.x == -1 || sel.mode == SEL_EMPTY)
+ return;
+
+ if (sel.alt != IS_SET(MODE_ALTSCREEN))
+ return;
+
+ sel.nb.y += delta;
+ sel.ne.y += delta;
+ sel.ob.y += delta;
+ sel.oe.y += delta;
+
+ sb_view_changed();
+}
+
+static Line
+emptyline(void)
+{
+ static Line empty;
+ static int empty_cols;
+ int i = 0;
+
+ if (empty_cols != term.col) {
+ free(empty);
+ empty = xmalloc(term.col * sizeof(Glyph));
+ empty_cols = term.col;
+ }
+
+ for (i = 0; i < term.col; i++) {
+ empty[i] = term.c.attr;
+ empty[i].u = ' ';
+ empty[i].mode = 0;
+ }
+ return empty;
+}
+
+static Line
+renderline(int y)
+{
+ int start, v;
+
+ if (sb.view_offset <= 0)
+ return term.line[y];
+
+ start = sb.len - sb.view_offset; /* can be negative */
+ v = start + y;
+
+ if (v < 0)
+ return emptyline();
+
+ if (v < sb.len)
+ return sb_get(v);
+
+ /* past scrollback -> into current screen */
+ v -= sb.len;
+ if (v >= 0 && v < term.row)
+ return term.line[v];
+
+ return emptyline();
+}
+
+static void
+sb_reset_on_clear(void)
+{
+ sb_clear();
+ sb_view_changed();
+ if (sel.ob.x != -1 && term.row > 0)
+ selclear();
+}
+
+int
+tisaltscreen(void)
+{
+ return IS_SET(MODE_ALTSCREEN);
+}
+
ssize_t
xwrite(int fd, const char *s, size_t len)
{
sel.ob.x = -1;
}
-int
-tlinelen(int y)
+static int
+tlinelen(Line line)
{
int i = term.col;
-
- 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;
}
+static int
+tlinelen_render(int y)
+{
+ return tlinelen(renderline(y));
+}
+
void
selstart(int col, int row, int snap)
{
/* expand selection over line breaks */
if (sel.type == SEL_RECTANGULAR)
return;
- i = tlinelen(sel.nb.y);
+ i = tlinelen_render(sel.nb.y);
if (i < sel.nb.x)
sel.nb.x = i;
- if (tlinelen(sel.ne.y) <= sel.ne.x)
+ if (tlinelen_render(sel.ne.y) <= sel.ne.x)
sel.ne.x = term.col - 1;
}
int newx, newy, xt, yt;
int delim, prevdelim;
const Glyph *gp, *prevgp;
+ Line line;
switch (sel.snap) {
case SNAP_WORD:
* Snap around if the word wraps around at the end or
* beginning of a line.
*/
- prevgp = &term.line[*y][*x];
+ prevgp = &renderline(*y)[*x];
prevdelim = ISDELIM(prevgp->u);
for (;;) {
newx = *x + direction;
yt = *y, xt = *x;
else
yt = newy, xt = newx;
- if (!(term.line[yt][xt].mode & ATTR_WRAP))
+ line = renderline(yt);
+ if (!(line[xt].mode & ATTR_WRAP))
break;
}
- if (newx >= tlinelen(newy))
+ if (newx >= tlinelen_render(newy))
break;
- gp = &term.line[newy][newx];
+ gp = &renderline(newy)[newx];
delim = ISDELIM(gp->u);
if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
|| (delim && gp->u != prevgp->u)))
*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 (!(renderline(*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 (!(renderline(*y)[term.col-1].mode
& ATTR_WRAP)) {
break;
}
getsel(void)
{
char *str, *ptr;
- int y, bufsize, lastx, linelen;
+ int y, bufsize, lastx, linelen, end_idx, insert_newline, is_wrapped;
const Glyph *gp, *last;
+ Line line;
if (sel.ob.x == -1)
return NULL;
/* append every set & selected glyph to the selection */
for (y = sel.nb.y; y <= sel.ne.y; y++) {
- if ((linelen = tlinelen(y)) == 0) {
+ line = renderline(y);
+ linelen = tlinelen_render(y);
+
+ if (linelen == 0) {
*ptr++ = '\n';
continue;
}
if (sel.type == SEL_RECTANGULAR) {
- gp = &term.line[y][sel.nb.x];
+ gp = &line[sel.nb.x];
lastx = sel.ne.x;
} else {
- gp = &term.line[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 = &term.line[y][MIN(lastx, linelen-1)];
- while (last >= gp && last->u == ' ')
+ end_idx = MIN(lastx, linelen-1);
+ is_wrapped = (line[end_idx].mode & ATTR_WRAP) != 0;
+ last = &line[end_idx];
+ while (last >= gp && last->u == ' ') {
--last;
+ }
for ( ; gp <= last; ++gp) {
if (gp->mode & ATTR_WDUMMY)
continue;
-
ptr += utf8encode(gp->u, ptr);
}
-
/*
* Copy and pasting of line endings is inconsistent
* in the inconsistent terminal and GUI world.
* st.
* FIXME: Fix the computer world.
*/
+ insert_newline = 0;
if ((y < sel.ne.y || lastx >= linelen) &&
- (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
+ (!is_wrapped || sel.type == SEL_RECTANGULAR)) {
+ insert_newline = 1;
+ }
+
+ if (insert_newline)
*ptr++ = '\n';
}
*ptr = 0;
{
const char *next;
+ if (sb.view_offset > 0) {
+ selclear();
+ sb.view_offset = 0;
+ sb_view_changed();
+ }
+
if (may_echo && IS_SET(MODE_ECHO))
twrite(s, n, 1);
{
int i;
- if (term.row <= 0)
+ if (term.row < 1)
return;
-
LIMIT(top, 0, term.row-1);
LIMIT(bot, 0, term.row-1);
for (i = 0; i < 2; i++) {
tmoveto(0, 0);
tcursor(CURSOR_SAVE);
- tclearregion(0, 0, term.col-1, term.row-1);
+ if (term.col > 0 && term.row > 0 && term.line > 0)
+ tclearregion(0, 0, term.col-1, term.row-1);
tswapscreen();
}
+ sb_clear();
+ if (sel.ob.x != -1 && term.row > 0)
+ selclear();
}
+
void
tnew(int col, int row)
{
term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
+ sb_init(scrollback_lines);
tresize(col, row);
treset();
}
tscrollup(int orig, int n)
{
int i;
+ uint64_t newstart;
+ uint64_t oldstart;
+
+ int attop;
Line temp;
+ oldstart = sb_view_start();
LIMIT(n, 0, term.bot-orig+1);
+ if (!IS_SET(MODE_ALTSCREEN) && orig == term.top) {
+ /* At top of history only if history exists */
+ attop = (sb.len != 0 && sb.view_offset == sb.len);
+
+ if (sb.view_offset > 0 && !attop)
+ sb.view_offset += n;
+
+ for (i = 0; i < n; i++)
+ sb_push(term.line[orig + i]);
+
+ /* if at the top, keep me there */
+ if (attop)
+ sb.view_offset = sb.len;
+ /* otherwise clamp me */
+ else if (sb.view_offset > sb.len)
+ sb.view_offset = sb.len;
+ }
+
+ newstart = sb_view_start();
+ if (sb.view_offset > 0)
+ selscrollback(oldstart - newstart);
+
tclearregion(0, orig, term.col-1, orig+n-1);
tsetdirt(orig+n, term.bot);
void
selscroll(int orig, int n)
{
+ if (sb.view_offset != 0)
+ return;
if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
return;
} 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) {
- selclear();
- } else {
- selnormalize();
- }
+ selnormalize();
}
}
break;
case 2: /* all */
tclearregion(0, 0, term.col-1, term.row-1);
+ if (!IS_SET(MODE_ALTSCREEN))
+ sb_reset_on_clear();
+ break;
+ case 3:
+ if (!IS_SET(MODE_ALTSCREEN))
+ sb_reset_on_clear();
break;
default:
goto unknown;
const Glyph *bp, *end;
bp = &term.line[n][0];
- end = &bp[MIN(tlinelen(n), term.col) - 1];
+ end = &bp[MIN(tlinelen_render(n), term.col) - 1];
if (bp != end || bp->u != ' ') {
for ( ; bp <= end; ++bp)
tprinter(buf, utf8encode(bp->u, buf));
}
}
+static void
+kscroll(const Arg *arg)
+{
+ uint64_t oldstart;
+ uint64_t newstart;
+
+ oldstart = sb_view_start();
+ sb.view_offset += arg->i;
+ LIMIT(sb.view_offset, 0, sb.len);
+ newstart = sb_view_start();
+ selscrollback(oldstart - newstart);
+ redraw();
+}
+
+void
+kscrolldown(const Arg *arg)
+{
+ Arg a;
+
+ if (arg->i < 0)
+ a.i = -term.row;
+ else
+ a.i = -arg->i;
+
+ kscroll(&a);
+}
+
+void
+kscrollup(const Arg *arg)
+{
+ Arg a;
+
+ if (arg->i < 0)
+ a.i = term.row;
+ else
+ a.i = arg->i;
+
+ kscroll(&a);
+}
+
void
tdectest(char c)
{
void
tresize(int col, int row)
{
- int i;
+ int i, j;
+ int min_limit;
int minrow = MIN(row, term.row);
- int mincol = MIN(col, term.col);
- int *bp;
- TCursor c;
+ int old_row = term.row;
+ int old_col = term.col;
+ int save_end = 0; /* Track effective pushed height */
+ int loaded = 0;
+ int pop_width = 0;
+ int needs_reflow = 0;
+ int is_alt = IS_SET(MODE_ALTSCREEN);
+ Line *tmp;
if (col < 1 || row < 1) {
fprintf(stderr,
- "tresize: error resizing to %dx%d\n", col, row);
+ "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]);
- }
- /* 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));
- }
- for (i += row; i < term.row; i++) {
- free(term.line[i]);
- free(term.alt[i]);
+ if (sel.ob.x != -1)
+ selclear();
+
+ /* Operate on the currently visible screen buffer. */
+ if (is_alt) {
+ tmp = term.line;
+ term.line = term.alt;
+ term.alt = tmp;
}
- /* 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));
+ save_end = term.row;
+ if (term.row != 0 && term.col != 0) {
+ if (!is_alt && term.c.y > 0 && term.c.y < term.row) {
+ term.line[term.c.y - 1][term.col - 1].mode &= ~ATTR_WRAP;
+ }
+ min_limit = is_alt ? 0 : term.c.y;
- /* 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));
- }
+ for (i = term.row - 1; i > min_limit; i--) {
+ if (tlinelen(term.line[i]) > 0)
+ break;
+ }
+ save_end = i + 1;
- /* 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 = 0; i < save_end; i++) {
+ sb_push(term.line[i]);
+ }
+ /* Optimization: Only reflow if content doesn't fit in new width.
+ * This avoids expensive reflow operations when resizing doesn't
+ * affect line wrapping (e.g., when terminal is wide enough). */
+ if (col > term.col) {
+ /* Growing: Only reflow if history was wrapped at old width */
+ needs_reflow = sb.max_width >= term.col;
+ } else if (col < term.col) {
+ /* Shrinking: Only reflow if content is wider than new width. */
+ if (sb.max_width > col)
+ needs_reflow = 1;
+ }
+ if (needs_reflow) {
+ sb_resize(col);
+ } else {
+ /* If we don't reflow, we still need to reset the view
+ * because sb_pop_screen() might change the history length. */
+ sb.view_offset = 0;
+ }
}
- 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 */
+ if (term.line) {
+ for (i = 0; i < term.row; i++) {
+ free(term.line[i]);
+ free(term.alt[i]);
+ }
+ free(term.line);
+ free(term.alt);
+ free(term.dirty);
+ free(term.tabs);
+ }
+
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 (0 < col && minrow < row) {
- tclearregion(0, minrow, col - 1, row - 1);
+
+ term.line = xmalloc(term.row * sizeof(Line));
+ term.alt = xmalloc(term.row * sizeof(Line));
+ term.dirty = xmalloc(term.row * sizeof(int));
+ term.tabs = xmalloc(term.col * sizeof(*term.tabs));
+
+ for (i = 0; i < term.row; i++) {
+ term.line[i] = xmalloc(term.col * sizeof(Glyph));
+ term.alt[i] = xmalloc(term.col * sizeof(Glyph));
+ term.dirty[i] = 1;
+
+ for (j = 0; j < term.col; j++) {
+ term.line[i][j] = term.c.attr;
+ term.line[i][j].u = ' ';
+ term.line[i][j].mode = 0;
+
+ term.alt[i][j] = term.c.attr;
+ term.alt[i][j].u = ' ';
+ term.alt[i][j].mode = 0;
}
- tswapscreen();
- tcursor(CURSOR_LOAD);
}
- term.c = c;
+
+ memset(term.tabs, 0, term.col * sizeof(*term.tabs));
+ for (i = 8; i < term.col; i += 8)
+ term.tabs[i] = 1;
+
+ tsetscroll(0, term.row - 1);
+
+ if (minrow > 0) {
+ loaded = MIN(sb.len, term.row);
+ pop_width = needs_reflow ? col : MIN(col, old_col);
+ sb_pop_screen(loaded, pop_width);
+ }
+ if (is_alt) {
+ tmp = term.line;
+ term.line = term.alt;
+ term.alt = tmp;
+ }
+ if (!is_alt && old_row > 0) {
+ term.c.y += (loaded - save_end);
+ }
+ if (term.c.y >= term.row) {
+ term.c.y = term.row - 1;
+ }
+ if (term.c.x >= term.col) {
+ term.c.x = term.col - 1;
+ }
+ if (term.c.y < 0) {
+ term.c.y = 0;
+ }
+ if (term.c.x < 0) {
+ term.c.x = 0;
+ }
+
+ tfulldirt();
+ sb_view_changed();
}
void
{
int y;
+ Line line;
for (y = y1; y < y2; y++) {
if (!term.dirty[y])
continue;
-
term.dirty[y] = 0;
- xdrawline(term.line[y], x1, y, x2);
+ line = renderline(y);
+ xdrawline(line, x1, y, x2);
}
}
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]);
- term.ocx = cx;
- term.ocy = term.c.y;
+ if (sb.view_offset == 0) {
+ xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
+ term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
+ term.ocx = cx;
+ term.ocy = term.c.y;
+ }
xfinishdraw();
if (ocx != term.ocx || ocy != term.ocy)
xximspot(term.ocx, term.ocy);