From: Kirill A. Korinsky Subject: Re: bin/ksh: fix emacs/vi line editor getting wrong terminal width To: Helg Cc: tech@openbsd.org Date: Wed, 21 Jan 2026 03:17:50 +0100 On Mon, 12 Jan 2026 07:27:03 +0100, Helg wrote: > > On Sun, Jan 11, 2026 at 12:58:46AM +0100, Kirill A. Korinsky wrote: > > tech@, > > > > Xterm and other terminal emulators resize the pty after starting the > > shell. x_init() queries terminal size at startup before the resize > > occurs, so x_cols is kept at default 80 colums. > > > > SIGWINCH arrives but check_sigwinch() wasn't called until after command > > entry. This causes the line editor thinks that screen is 80 columns, > > than leads to show '<' and truncate input before reaching the actual > > terminal edge for large window, and wired behaiour for small one. > > > > Here, I call check_sigwinch() when entering edit mode and on EINTR in > > x_getc(). Callback lets emacs/vi recalculate display width and redraw. > > > > Thoughts? Tests? Ok? > > This patch is heading in the right direction but I've tested it in both > X11 and the MacOS Terminal application and it still needs some tweaks. > > The following is the output when I resize MacOS Terminal to 64 > characters wide. > > apple$ /usr/src/bin/ksh/ksh > asd we rewr ewr qr qewrt > <<<<<<<<<<<<<<< > > You can see that it is adding multiple chevrons, when before it didn't > do that. Also, if the cursor is close to the right when you resize, > the chevron disappears or is replaced by an asterisk. It's likely there > is an off by one error somewhere. May I ask you to try this one? Index: edit.c =================================================================== RCS file: /home/cvs/src/bin/ksh/edit.c,v diff -u -p -r1.71 edit.c --- edit.c 23 Apr 2024 13:34:50 -0000 1.71 +++ edit.c 21 Jan 2026 02:16:58 -0000 @@ -25,7 +25,8 @@ X_chars edchars; static void x_sigwinch(int); volatile sig_atomic_t got_sigwinch; -static void check_sigwinch(void); +void check_sigwinch(void); +void (*x_resize_cb)(void); /* callback for resize during edit */ static int x_file_glob(int, const char *, int, char ***); static int x_command_glob(int, const char *, int, char ***); @@ -58,7 +59,7 @@ x_sigwinch(int sig) got_sigwinch = 1; } -static void +void check_sigwinch(void) { if (got_sigwinch) { @@ -75,11 +76,16 @@ check_sigwinch(void) * see the change cause the environ doesn't change. */ if (ws.ws_col) { + int old_cols = x_cols; + x_cols = ws.ws_col < MIN_COLS ? MIN_COLS : ws.ws_col; if ((vp = typeset("COLUMNS", 0, 0, 0, 0))) setint(vp, (int64_t) ws.ws_col); + + if (x_cols != old_cols && x_resize_cb) + (*x_resize_cb)(); } if (ws.ws_row && (vp = typeset("LINES", 0, 0, 0, 0))) setint(vp, (int64_t) ws.ws_row); @@ -120,12 +126,14 @@ x_getc(void) char c; int n; - while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR) + while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR) { + check_sigwinch(); if (trap) { x_mode(false); runtraps(0); x_mode(true); } + } if (n != 1) return -1; return (int) (unsigned char) c; Index: edit.h =================================================================== RCS file: /home/cvs/src/bin/ksh/edit.h,v diff -u -p -r1.13 edit.h --- edit.h 21 Jun 2023 22:22:08 -0000 1.13 +++ edit.h 21 Jan 2026 02:16:58 -0000 @@ -39,6 +39,8 @@ extern X_chars edchars; #define XCF_COMMAND_FILE (XCF_COMMAND|XCF_FILE) /* edit.c */ +void check_sigwinch(void); +extern void (*x_resize_cb)(void); int x_getc(void); void x_flush(void); int x_putc(int); Index: emacs.c =================================================================== RCS file: /home/cvs/src/bin/ksh/emacs.c,v diff -u -p -r1.90 emacs.c --- emacs.c 21 Jun 2023 22:22:08 -0000 1.90 +++ emacs.c 21 Jan 2026 02:16:58 -0000 @@ -105,6 +105,7 @@ static int xlp_valid; /* end from 4.9 edit.h } */ static int x_tty; /* are we on a tty? */ static int x_bind_quiet; /* be quiet when binding keys */ +static void x_emacs_resize(void); static int (*x_last_command)(int); static char **x_histp; /* history position */ @@ -286,6 +287,8 @@ x_emacs(char *buf, size_t len) xmp = NULL; x_histp = histptr + 1; + x_resize_cb = x_emacs_resize; + check_sigwinch(); xx_cols = x_cols; x_col = promptlen(prompt, &p); prompt_skip = p - prompt; @@ -316,8 +319,11 @@ x_emacs(char *buf, size_t len) x_last_command = NULL; while (1) { x_flush(); - if ((at = x_e_getu8(line, at)) < 0) + if ((at = x_e_getu8(line, at)) < 0) { + x_resize_cb = NULL; return 0; + } + ntries++; if (x_arg == -1) { @@ -378,6 +384,7 @@ x_emacs(char *buf, size_t len) x_last_command = NULL; break; case KEOL: + x_resize_cb = NULL; ret = xep - xbuf; return (ret); break; @@ -2160,6 +2167,39 @@ x_lastcp(void) } xlp_valid = true; return (xlp); +} + +static void +x_emacs_resize(void) +{ + /* clear old content before resizing */ + x_putc('\r'); + x_putc('\033'); + x_putc('['); + x_putc('J'); + + xx_cols = x_cols; + x_col = promptlen(prompt, NULL); + if (x_col > xx_cols) + x_col = x_col - (x_col / xx_cols) * xx_cols; + x_displen = xx_cols - 2 - x_col; + if (x_displen < 1) { + x_col = 0; + x_displen = xx_cols - 2; + prompt_redraw = 0; + } else { + xbp = xbuf; + xlp_valid = false; + if (xcp <= x_lastcp()) { + x_redraw(0); + x_flush(); + return; + } + } + + x_col = 0; + x_displen = xx_cols - 2; + x_adjust(); } #endif /* EMACS */ Index: vi.c =================================================================== RCS file: /home/cvs/src/bin/ksh/vi.c,v diff -u -p -r1.67 vi.c --- vi.c 20 Jul 2025 21:24:07 -0000 1.67 +++ vi.c 21 Jan 2026 02:16:58 -0000 @@ -75,6 +75,8 @@ static void vi_error(void); static void vi_macro_reset(void); static int x_vi_putbuf(const char *, size_t); static int isu8cont(unsigned char); +static void x_vi_resize(void); +static void calc_winsize(void); #define C_ 0x1 /* a valid command that isn't a M_, E_, U_ */ #define M_ 0x2 /* movement command (h, l, etc.) */ @@ -201,6 +203,8 @@ x_vi(char *buf, size_t len) int c; vi_reset(buf, len > LINE ? LINE : len); + x_resize_cb = x_vi_resize; + check_sigwinch(); vi_pprompt(1); x_flush(); while (1) { @@ -242,6 +246,7 @@ x_vi(char *buf, size_t len) x_flush(); } + x_resize_cb = NULL; x_putc('\r'); x_putc('\n'); x_flush(); if (c == -1 || len <= (size_t)es->linelen) @@ -1424,8 +1429,6 @@ free_edstate(struct edstate *old) static void edit_reset(char *buf, size_t len) { - const char *p; - es = &ebuf; es->cbuf = buf; es->cbufsize = len; @@ -1436,6 +1439,17 @@ edit_reset(char *buf, size_t len) es->cursor = undo->cursor = 0; es->winleft = undo->winleft = 0; + calc_winsize(); + win = 0; + morec = ' '; + holdlen = 0; +} + +static void +calc_winsize(void) +{ + const char *p; + cur_col = pwidth = promptlen(prompt, &p); prompt_skip = p - prompt; if (pwidth > x_cols - 3 - MIN_EDIT_SPACE) { @@ -1452,9 +1466,6 @@ edit_reset(char *buf, size_t len) (void) memset(wbuf[0], ' ', wbuf_len); (void) memset(wbuf[1], ' ', wbuf_len); winwidth = x_cols - pwidth - 3; - win = 0; - morec = ' '; - holdlen = 0; } /* @@ -2299,5 +2310,30 @@ static int isu8cont(unsigned char c) { return !Flag(FVISHOW8) && (c & (0x80 | 0x40)) == 0x80; +} + +static void +x_vi_resize(void) +{ + int cur = 0, col = 0; + + /* clear old content before resizing */ + x_putc('\r'); + x_putc('\033'); + x_putc('['); + x_putc('J'); + + calc_winsize(); + + while (cur < es->linelen) + col = newcol((unsigned char) es->cbuf[cur++], col); + if (col <= winwidth) + es->winleft = 0; + else if (outofwin()) + rewindow(); + + redraw_line(0, 0); + refresh_line(insert != 0); + x_flush(); } #endif /* VI */ -- wbr, Kirill