commit dcff8b64e7e03fb1a73fd4f215299851860f6912
parent ae8617668bacced0142bd508bfaaeecfabf92b11
Author: Andrew Kloet <andrew@kloet.net>
Date: Wed, 17 Jun 2026 13:12:36 -0400
ui: refactor text rendering to use cached visual lines
Replace on-the-fly line wrapping with a structural VisLine array cache
per channel. Fixes dynamic word-wrap and boundary bugs during scrolling
and terminal resizing. Clean up variable scoping and tracking structures.
Diffstat:
| M | cio.c | | | 129 | +++++++++++++++++++++++++++++++++++++++++++++++++------------------------------ |
1 file changed, 80 insertions(+), 49 deletions(-)
diff --git a/cio.c b/cio.c
@@ -97,15 +97,23 @@ static struct {
WINDOW *sw, *mw, *iw;
} scr;
+struct VisLine {
+ size_t len, offset; /* Offset from Chan->buf */
+ unsigned char cont;
+};
+
static struct Chan {
char name[ChanLen];
char *buf, *eol;
- int n; /* Scroll offset. */
int last_mday; /* Tracks day of last message */
size_t sz; /* Size of buf. */
unsigned char high; /* Nick highlight. */
unsigned char new; /* New message. */
unsigned char join; /* Channel was 'j'-oined. */
+ struct VisLine *vis;
+ size_t vis_n;
+ size_t vis_sz;
+ int vis_off; /* Scroll offset in visual lines */
} chl[MaxChans];
static struct {
@@ -176,8 +184,6 @@ static int
srd(void)
{
static char l[BufSz], *p = l;
- char *s, *usr, *cmd, *argv[MaxParams];
- int argc;
ssize_t rd;
if (p - l >= BufSz)
@@ -189,13 +195,13 @@ srd(void)
if (rd <= 0) return 0;
p += rd;
for (;;) { /* Cycle on all received lines. */
- s = memchr(l, '\n', p - l);
+ char *s = memchr(l, '\n', p - l);
if (!s) return 1;
if (s > l && s[-1] == '\r') s[-1] = 0;
*s++ = 0;
- char *line = l, *token;
- usr = NULL;
- argc = 0;
+ char *line = l, *token, *usr = NULL, *cmd;
+ int argc = 0;
+ char *argv[MaxParams];
if (*line == ':') {
usr = line + 1;
strsep(&line, " ");
@@ -216,10 +222,10 @@ srd(void)
}
static void
-sinit(const char *nick, const char *user)
+sinit(const char *init_nick, const char *user)
{
sndf("CAP LS 302");
- sndf("NICK %s", nick);
+ sndf("NICK %s", init_nick);
sndf("USER %s 8 * :%s", user, user);
}
@@ -309,9 +315,12 @@ chadd(const char *name, int joined)
if (!chl[nch].buf)
die("cio: malloc:");
chl[nch].eol = chl[nch].buf;
- chl[nch].n = 0;
chl[nch].join = joined;
chl[nch].last_mday = -1;
+ chl[nch].vis = NULL;
+ chl[nch].vis_n = 0;
+ chl[nch].vis_sz = 0;
+ chl[nch].vis_off = 0;
if (joined)
ch = nch;
nch++;
@@ -325,6 +334,7 @@ chdel(const char *name)
int n = chfind(name);
if (n <= 0) return 0;
free(chl[n].buf);
+ free(chl[n].vis);
if (n < nch - 1) { /* Move trailing channels left by one */
size_t len = (nch - n - 1) * sizeof(struct Chan);
@@ -337,39 +347,55 @@ chdel(const char *name)
return 1;
}
+static void
+add_visline(struct Chan *c, size_t offset, size_t len, int cont)
+{
+ if (c->vis_n >= c->vis_sz) {
+ c->vis_sz = c->vis_sz ? c->vis_sz * 2 : 1024;
+ c->vis = realloc(c->vis, c->vis_sz * sizeof(*c->vis));
+ if (!c->vis) die("vis realloc:");
+ }
+ c->vis[c->vis_n].offset = offset;
+ c->vis[c->vis_n].len = len;
+ c->vis[c->vis_n].cont = cont;
+ c->vis_n++;
+}
+
static char *
-pushl(char *p, char *e)
+pushl(struct Chan *c, char *p, char *e)
{
size_t x = 0;
wchar_t wc;
- int n, cl;
- cchar_t cc;
+ int cl;
+ int cont = 0;
char *eol = memchr(p, '\n', e - p);
+ const char *offset = p;
if (!eol) eol = e;
mbtowc(NULL, NULL, 0);
while (p < eol) {
- n = mbtowc(&wc, p, eol - p);
- if (n <= 0) {
- mbtowc(NULL, NULL, 0);
- wc = L'?'; n = 1;
- }
+ int n = mbtowc(&wc, p, eol - p);
+ if (n <= 0) { wc = L'?'; n = 1; }
if (iswcntrl(wc)) {
p += n;
continue;
}
+
cl = wcwidth(wc);
if (cl < 0) cl = 0;
+
if (x + cl >= (size_t)scr.x) {
- waddch(scr.mw, '\n');
- for (x = 0; x < INDENT; x++)
- waddch(scr.mw, ' ');
+ add_visline(c, offset - c->buf, p - offset, cont);
+ offset = p;
+ cont = 1;
+ x = INDENT;
}
- setcchar(&cc, &wc, 0, 0, 0);
- wadd_wch(scr.mw, &cc);
x += cl;
p += n;
}
+
+ if (p > offset)
+ add_visline(c, offset - c->buf, p - offset, cont);
return (eol < e) ? eol + 1 : e;
}
@@ -419,13 +445,10 @@ pushf(int cn, const char *fmt, ...)
c->eol += n;
*c->eol++ = '\n';
*c->eol = '\0';
- if (cn == ch && c->n == 0) {
+ if (c->vis_off == 0) {
char *p = c->eol - n - 1;
-
- if (p != c->buf)
- waddch(scr.mw, '\n');
- pushl(p, c->eol - 1);
- wnoutrefresh(scr.mw);
+ pushl(c, p, c->eol - 1);
+ tredraw();
}
}
@@ -450,7 +473,7 @@ scmd(char *usr, char *cmd, int argc, char **argv)
case NOTICE: /* FALLTHROUGH */
case PRIVMSG:
if (argc < 2) break;
- char *chan;
+ const char *chan;
if (strchr("&#!+.~", argv[0][0])) chan = argv[0];
else if (type == PRIVMSG) chan = usr;
else chan = NULL;
@@ -666,6 +689,12 @@ tresize(void)
wresize(scr.iw, 1, scr.x);
wresize(scr.sw, 1, scr.x);
mvwin(scr.iw, scr.y - 1, 0);
+ for (int i = 0; i < nch; i++){
+ chl[i].vis_n = 0;
+ char *p = chl[i].buf;
+ while (p && p < chl[i].eol)
+ p = pushl(&chl[i], p, chl[i].eol);
+ }
tredraw();
tdrawbar();
tdrawinput();
@@ -675,24 +704,26 @@ tresize(void)
static void
tredraw(void)
{
- struct Chan *const c = &chl[ch];
- char *p = c->eol, *start = c->buf;
- int msg_count = 0;
+ struct Chan *c = &chl[ch];
+ int height = scr.y - 2;
wclear(scr.mw);
- if (c->eol == c->buf) return;
- while (p > c->buf && msg_count < (scr.y - 2 + c->n)) {
- char *s = p - 1;
- while (s > c->buf && *(s - 1) != '\n') s--;
- if (msg_count >= c->n) start = s;
- p = s - 1;
- msg_count++;
+ if (c->vis_n == 0) {
+ wnoutrefresh(scr.mw);
+ return;
}
- p = start;
- for (int i = 0; p < c->eol && i < (scr.y - 2); i++) {
- char *next = pushl(p, c->eol);
- if (next < c->eol) waddch(scr.mw, '\n');
- p = next;
+ if (c->vis_off > (int)c->vis_n - height)
+ c->vis_off = (int)c->vis_n - height;
+ if (c->vis_off < 0)
+ c->vis_off = 0;
+ int offset = (int)c->vis_n - height - c->vis_off;
+ if (offset < 0)
+ offset = 0;
+ for (int i = offset; i < (int)c->vis_n && i < offset + height; i++) {
+ if (c->vis[i].cont)
+ wmove(scr.mw, getcury(scr.mw), INDENT);
+ waddnstr(scr.mw, c->buf + c->vis[i].offset, c->vis[i].len);
+ waddch(scr.mw, '\n');
}
wnoutrefresh(scr.mw);
}
@@ -796,9 +827,7 @@ tgetch(void)
return;
case KEY_PPAGE: /* FALLTHROUGH */
case KEY_NPAGE:
- chl[ch].n += (wc == KEY_PPAGE) ? SCROLL : -SCROLL;
- if (chl[ch].n < 0)
- chl[ch].n = 0;
+ chl[ch].vis_off += (wc == KEY_PPAGE) ? SCROLL : -SCROLL;
tredraw();
return;
case CTRL('a'):
@@ -978,7 +1007,9 @@ main(int argc, char *argv[])
}
}
hangup();
- while (nch--)
+ while (nch--) {
free(chl[nch].buf);
+ free(chl[nch].vis);
+ }
exit(0);
}