From 8494bea38fa6da821cc6d7103cdf227bf3aadc8c Mon Sep 17 00:00:00 2001 From: Adam Saponara Date: Sat, 4 Apr 2026 13:20:21 -0400 Subject: [PATCH] Add `linenoiseResize` Currently if the terminal is resized in the middle of a line edit, then `cols` goes stale and the output is garbled. In regular mode, the only way to detect this would be to query terminal size after every input (bad), or to install a signal handler for `SIGWINCH` (bigger change). In async mode, the caller can install their own signal handler, but has no way to tell the library to refetch terminal size. This function allows the caller to do that. To demo this, add `--async-resize` flag to the example program. Also replace tabs in example.c with spaces. Previously it was mixed. --- example.c | 76 +++++++++++++++++++++++++++++++++++++++-------------- linenoise.c | 5 ++++ linenoise.h | 1 + 3 files changed, 62 insertions(+), 20 deletions(-) diff --git a/example.c b/example.c index 3a7f8b37..beeb16b5 100644 --- a/example.c +++ b/example.c @@ -1,9 +1,14 @@ #include #include #include +#include +#include +#include #include #include "linenoise.h" +int resizePipe[2] = {-1, -1}; + void completion(const char *buf, linenoiseCompletions *lc) { if (buf[0] == 'h') { linenoiseAddCompletion(lc,"hello"); @@ -20,10 +25,26 @@ char *hints(const char *buf, int *color, int *bold) { return NULL; } +void handleResize(int sig) { + write(resizePipe[1], &sig, sizeof(sig)); +} + +int makeResizePipe(void) { + pipe(resizePipe); + + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handleResize; + sigaction(SIGWINCH, &sa, NULL); + + return resizePipe[0]; +} + int main(int argc, char **argv) { char *line; char *prgname = argv[0]; int async = 0; + int async_resize = 0; /* Parse options, with --multiline we enable multi line editing. */ while(argc > 1) { @@ -37,8 +58,10 @@ int main(int argc, char **argv) { exit(0); } else if (!strcmp(*argv,"--async")) { async = 1; + } else if (!strcmp(*argv,"--async-resize")) { + async_resize = 1; } else { - fprintf(stderr, "Usage: %s [--multiline] [--keycodes] [--async]\n", prgname); + fprintf(stderr, "Usage: %s [--multiline] [--keycodes] [--async] [--async-resize]\n", prgname); exit(1); } } @@ -69,34 +92,47 @@ int main(int argc, char **argv) { * using the select(2) timeout. */ struct linenoiseState ls; char buf[1024]; + int resizefd = async_resize ? makeResizePipe() : -1; linenoiseEditStart(&ls,-1,-1,buf,sizeof(buf),"hello> "); while(1) { - fd_set readfds; - struct timeval tv; - int retval; - - FD_ZERO(&readfds); - FD_SET(ls.ifd, &readfds); - tv.tv_sec = 1; // 1 sec timeout - tv.tv_usec = 0; - - retval = select(ls.ifd+1, &readfds, NULL, NULL, &tv); - if (retval == -1) { - perror("select()"); - exit(1); - } else if (retval) { - line = linenoiseEditFeed(&ls); + fd_set readfds; + struct timeval tv; + int retval, maxfd; + + FD_ZERO(&readfds); + FD_SET(ls.ifd, &readfds); + tv.tv_sec = 1; // 1 sec timeout + tv.tv_usec = 0; + maxfd = ls.ifd; + + if (resizefd > 0) { + FD_SET(resizefd, &readfds); + if (resizefd > maxfd) maxfd = resizefd; + } + + retval = select(maxfd+1, &readfds, NULL, NULL, &tv); + if (retval == -1) { + if (errno != EINTR) { + perror("select()"); + exit(1); + } + } else if (FD_ISSET(ls.ifd, &readfds)) { + line = linenoiseEditFeed(&ls); /* A NULL return means: line editing is continuing. * Otherwise the user hit enter or stopped editing * (CTRL+C/D). */ if (line != linenoiseEditMore) break; - } else { - // Timeout occurred + } else if (FD_ISSET(resizefd, &readfds)) { + int ignore; + read(resizefd, &ignore, sizeof(ignore)); + linenoiseResize(&ls); + } else { + // Timeout occurred static int counter = 0; linenoiseHide(&ls); - printf("Async output %d.\n", counter++); + printf("Async output %d.\n", counter++); linenoiseShow(&ls); - } + } } linenoiseEditStop(&ls); if (line == NULL) exit(0); /* Ctrl+D/C. */ diff --git a/linenoise.c b/linenoise.c index db9b06cb..47fd0d93 100644 --- a/linenoise.c +++ b/linenoise.c @@ -1093,6 +1093,11 @@ void linenoiseShow(struct linenoiseState *l) { } } +/* In async mode, call this after terminal size has changed. */ +void linenoiseResize(struct linenoiseState *l) { + l->cols = getColumns(l->ifd, l->ofd); +} + /* Insert the character(s) 'c' of length 'clen' at cursor current position. * This handles both single-byte ASCII and multi-byte UTF-8 sequences. * diff --git a/linenoise.h b/linenoise.h index e56b6271..c29f85f7 100644 --- a/linenoise.h +++ b/linenoise.h @@ -80,6 +80,7 @@ char *linenoiseEditFeed(struct linenoiseState *l); void linenoiseEditStop(struct linenoiseState *l); void linenoiseHide(struct linenoiseState *l); void linenoiseShow(struct linenoiseState *l); +void linenoiseResize(struct linenoiseState *l); /* Blocking API. */ char *linenoise(const char *prompt);