Index | Thread | Search

From:
Walter Alejandro Iglesias <wai@roquesor.com>
Subject:
Re: bin/ksh: fix emacs/vi line editor getting wrong terminal width
To:
"Kirill A. Korinsky" <kirill@korins.ky>
Cc:
tech@openbsd.org
Date:
Wed, 21 Jan 2026 14:45:26 +0100

Download raw body.

Thread
  • Walter Alejandro Iglesias:

    bin/ksh: fix emacs/vi line editor getting wrong terminal width

  • Hi Kirill,
    
    As far as I tested this diff works for me.  I've tested it on xterm only.
    
    On Wed, Jan 21, 2026 at 03:17:50AM +0100, Kirill A. Korinsky wrote:
    > On Mon, 12 Jan 2026 07:27:03 +0100,
    > Helg <helg-openbsd@gmx.de> 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
    > 
    > 
    
    -- 
    Walter
    
    
  • Walter Alejandro Iglesias:

    bin/ksh: fix emacs/vi line editor getting wrong terminal width