cio

a simple irc client
git clone git://kloet.net/cio
Download | Log | Files | Refs | README | LICENSE

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:
Mcio.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); }