Index | Thread | Search

From:
Kirill A. Korinsky <kirill@korins.ky>
Subject:
Re: bin/ksh: fix emacs/vi mode using wrong terminal width
To:
tech@openbsd.org
Date:
Sun, 25 Jan 2026 17:23:12 +0100

Download raw body.

Thread
On Thu, 22 Jan 2026 16:59:21 +0100,
Kirill A. Korinsky <kirill@korins.ky> wrote:
> 
> tech@,
> 
> this is clear re-submission of my patch to ksh to fix terminal width
> detecion.
> 
> xterm and other terminal emulators resize the pty after starting the
> shell. x_init() queries the terminal size at startup before the resize
> occurs, so x_cols stays at the default 80 columns.
> 
> SIGWINCH arrives, but check_sigwinch() isn't called until after command
> entry. This makes the line editor think the screen is 80 columns, which
> makes it show '<' and truncate input before the actual terminal edge on
> large windows, and causes weird behavior on small ones.
> 
> Call check_sigwinch() when entering edit mode and on EINTR in x_getc().
> The callback lets emacs/vi mode recalculate display width and redraw.
> 

Here reworked version which addressed an edge case when prompt is duplicated
when resize had happened when curses application is used which was reported
by Walter Alejandro Iglesias, and suggestion from chris@ to honor curses
when it possible.

Also, I had introduced a regression tests with one note: the vi mode resize
path is multi stage and can interleave with pending input, so the byte
stream varies even when the final screen is correct; to make the test
deterministic, edit.c waits for two consecutive quiet poll timeouts to
ensure the child is idle before sending TIOCSWINSZ and SIGWINCH.

Ok?


diff --git bin/ksh/edit.c bin/ksh/edit.c
index 3ed69e5375d..83bab8ffbad 100644
--- bin/ksh/edit.c
+++ bin/ksh/edit.c
@@ -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;
diff --git bin/ksh/edit.h bin/ksh/edit.h
index 1d573e1f6fb..abcecc667e0 100644
--- bin/ksh/edit.h
+++ bin/ksh/edit.h
@@ -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);
diff --git bin/ksh/emacs.c bin/ksh/emacs.c
index 3c9b0074d81..43223bc06b2 100644
--- bin/ksh/emacs.c
+++ bin/ksh/emacs.c
@@ -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,9 @@ x_emacs(char *buf, size_t len)
 	xmp = NULL;
 	x_histp = histptr + 1;
 
+	x_resize_cb = NULL;
+	check_sigwinch();
+	x_resize_cb = x_emacs_resize;
 	xx_cols = x_cols;
 	x_col = promptlen(prompt, &p);
 	prompt_skip = p - prompt;
@@ -316,8 +320,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 +385,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;
@@ -2162,4 +2170,51 @@ x_lastcp(void)
 	return (xlp);
 }
 
+static void
+x_emacs_resize(void)
+{
+	int cr_done = 0, cleared = 0;
+
+#ifndef SMALL
+	if (cur_term != NULL) {
+		if (carriage_return != NULL &&
+		    tputs(carriage_return, 1, x_putc) != ERR)
+			cr_done = 1;
+		if (clr_eos != NULL &&
+		    tputs(clr_eos, 1, x_putc) != ERR)
+			cleared = 1;
+	}
+#endif
+	if (!cr_done)
+		x_putc('\r');
+	if (!cleared) {
+		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 */
diff --git bin/ksh/vi.c bin/ksh/vi.c
index 28be809036d..168a9a8ffa5 100644
--- bin/ksh/vi.c
+++ bin/ksh/vi.c
@@ -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.) */
@@ -200,6 +202,9 @@ x_vi(char *buf, size_t len)
 {
 	int	c;
 
+	x_resize_cb = NULL;
+	check_sigwinch();
+	x_resize_cb = x_vi_resize;
 	vi_reset(buf, len > LINE ? LINE : len);
 	vi_pprompt(1);
 	x_flush();
@@ -242,6 +247,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 +1430,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 +1440,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 +1467,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;
 }
 
 /*
@@ -2300,4 +2312,42 @@ isu8cont(unsigned char c)
 {
 	return !Flag(FVISHOW8) && (c & (0x80 | 0x40)) == 0x80;
 }
+
+static void
+x_vi_resize(void)
+{
+	int cur = 0, col = 0;
+	int cr_done = 0, cleared = 0;
+
+#ifndef SMALL
+	if (cur_term != NULL) {
+		if (carriage_return != NULL &&
+		    tputs(carriage_return, 1, x_putc) != ERR)
+			cr_done = 1;
+		if (clr_eos != NULL &&
+		    tputs(clr_eos, 1, x_putc) != ERR)
+			cleared = 1;
+	}
+#endif
+	if (!cr_done)
+		x_putc('\r');
+	if (!cleared) {
+		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 */
diff --git regress/bin/ksh/edit/edit.c regress/bin/ksh/edit/edit.c
index 7b49f371ab6..785dc5a251b 100644
--- regress/bin/ksh/edit/edit.c
+++ regress/bin/ksh/edit/edit.c
@@ -16,6 +16,7 @@
  */
 
 #include <sys/wait.h>
+#include <sys/ioctl.h>
 
 #include <err.h>
 #include <errno.h>
@@ -49,6 +50,7 @@ main(int argc, char *argv[])
 	ssize_t		 n;
 	size_t		 nin, nprompt, nread, nwrite;
 	int		 c, nready, ptyfd, readprompt, ret, status, timeout;
+	int		 pending_resize, pending_cols, pending_quiet, pending_post;
 
 	while ((c = getopt(argc, argv, "p:")) != -1) {
 		switch (c) {
@@ -96,14 +98,20 @@ main(int argc, char *argv[])
 	}
 
 	nprompt = nread = nwrite = ret = 0;
+	pending_resize = pending_cols = pending_quiet = pending_post = 0;
 	readprompt = 1;
 	while (!gotsig) {
 		pfd.fd = ptyfd;
-		if (!readprompt && nwrite < nin)
+		if (pending_resize || pending_post) {
+			pfd.events = POLLIN;
+			timeout = WRTIM;
+		} else if (!readprompt && nwrite < nin) {
 			pfd.events = POLLOUT;
-		else
+			timeout = WRTIM;
+		} else {
 			pfd.events = POLLIN;
-		timeout = readprompt ? PRTIM : WRTIM;
+			timeout = readprompt ? PRTIM : WRTIM;
+		}
 		nready = poll(&pfd, 1, timeout);
 		if (nready == -1) {
 			if (errno == EINTR)
@@ -111,6 +119,22 @@ main(int argc, char *argv[])
 			err(1, "poll");
 		}
 		if (nready == 0) {
+			if (pending_resize) {
+				if (++pending_quiet >= 2) {
+					ws.ws_col = pending_cols;
+					if (ioctl(ptyfd, TIOCSWINSZ, &ws) == -1)
+						err(1, "ioctl TIOCSWINSZ");
+					kill(pid, SIGWINCH);
+					pending_resize = pending_cols = 0;
+					pending_quiet = 0;
+					pending_post = 1;
+				}
+				continue;
+			}
+			if (pending_post) {
+				pending_post = 0;
+				continue;
+			}
 			if (timeout == PRTIM) {
 				warnx("timeout waiting from prompt");
 				ret = 1;
@@ -136,6 +160,28 @@ main(int argc, char *argv[])
 				readprompt = 0;
 			}
 		} else if (pfd.revents & POLLOUT) {
+			if (in[nwrite] == '\0' && nwrite + 3 < nin &&
+			    in[nwrite + 1] == 'R' &&
+			    in[nwrite + 2] == 'S' &&
+			    in[nwrite + 3] >= '0' && in[nwrite + 3] <= '9') {
+				size_t i = nwrite + 3;
+				int cols = 0, nd = 0;
+
+				while (i < nin && nd < 3 &&
+				    in[i] >= '0' && in[i] <= '9') {
+					cols = cols * 10 + (in[i] - '0');
+					i++;
+					nd++;
+				}
+				if (nd > 0 && (i == nin ||
+				    in[i] < '0' || in[i] > '9') &&
+				    cols > 0) {
+					pending_resize = 1;
+					pending_cols = cols;
+					nwrite = i;
+					continue;
+				}
+			}
 			if (strchr(linefeed, in[nwrite]) != NULL)
 				readprompt = 1;
 
@@ -143,6 +189,8 @@ main(int argc, char *argv[])
 			if (n == -1)
 				err(1, "write");
 			nwrite += n;
+			if (pending_resize)
+				pending_quiet = 0;
 		}
 	}
 	close(ptyfd);
diff --git regress/bin/ksh/edit/emacs.sh regress/bin/ksh/edit/emacs.sh
index abd664664bb..6ae4184e928 100644
--- regress/bin/ksh/edit/emacs.sh
+++ regress/bin/ksh/edit/emacs.sh
@@ -52,6 +52,30 @@ testseq "z\0002\0363\0277\0277\0277" " # z\b\0363\0277\0277\0277z\b"
 testseq "z\0002\0364\0200\0200\0200" " # z\b\0364\0200\0200\0200z\b"
 testseq "z\0002\0364\0217\0277\0277" " # z\b\0364\0217\0277\0277z\b"
 
+# ASCII resize: cols=1/11/12
+testseq "aaaaaaaaaabbbbbbbbbb\0001\000RS1" \
+	" # aaaaaaaaaabbbbbbbbbb\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r\033[J\r # aaaaaaa>\b\b\b\b\b\b\b\b"
+testseq "aaaaaaaaaabbbbbbbbbb\003310\0002\000RS11" \
+	" # aaaaaaaaaabbbbbbbbbb\b\b\b\b\b\b\b\b\b\b\r\033[J\raaaaabbbbb*\b\b\b\b\b\b"
+testseq "aaaaaaaaaabbbbbbbbbb\000RS12" \
+	" # aaaaaaaaaabbbbbbbbbb\r\033[J\rbbbbb     <\b\b\b\b\b\b"
+
+# ASCII resize: cols=13, cursor -1/0/+1
+testseq "aaaaaaaaaabbbbbbbbbb\003313\0002\000RS13" \
+	" # aaaaaaaaaabbbbbbbbbb\b\b\b\b\b\b\b\b\b\b\b\b\b\r\033[J\r # aaaaaaaa>\b\b"
+testseq "aaaaaaaaaabbbbbbbbbb\003312\0002\000RS13" \
+	" # aaaaaaaaaabbbbbbbbbb\b\b\b\b\b\b\b\b\b\b\b\b\r\033[J\r # aaaaaaaa>\b"
+testseq "aaaaaaaaaabbbbbbbbbb\003311\0002\000RS13" \
+	" # aaaaaaaaaabbbbbbbbbb\b\b\b\b\b\b\b\b\b\b\b\r\033[J\raaaaaabbbbb*\b\b\b\b\b\b\b"
+
+# ASCII resize: cols=79/80/81
+testseq "aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccc\0001\000RS79" \
+	" # aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccc\rbbbbbbbbbbbbccccccccccccccccccccccccc                                         <\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r # aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccc \b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r\033[J\r # aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccccc>\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"
+testseq "aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccc\0001\000RS80" \
+	" # aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccc\rbbbbbbbbbbbbccccccccccccccccccccccccc                                         <\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r # aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccc \b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"
+testseq "aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccc\0001\000RS81" \
+	" # aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccc\rbbbbbbbbbbbbccccccccccccccccccccccccc                                         <\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r # aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccc \b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r\033[J\r # aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccc \b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"
+
 # insertion of incomplete UTF-8
 testseq "z\0002\0302\0006" " # z\b\0302z\bz"
 testseq "z\0002\0377\0006" " # z\b\0377z\bz"
diff --git regress/bin/ksh/edit/vi.sh regress/bin/ksh/edit/vi.sh
index a4baa9ccf60..b32c9f7a566 100644
--- regress/bin/ksh/edit/vi.sh
+++ regress/bin/ksh/edit/vi.sh
@@ -201,3 +201,27 @@ testseq "(x)\00330%hrc" " # (x)\b\b\b(x\bc\b"
 
 # ^R: Redraw.
 testseq "test\0033h\0022" " # test\b\b\r\r\n # test\b\b"
+
+# ASCII resize: cols=1/11/12
+testseq "aaaaaaaaaabbbbbbbbbb\0033hhhhhhhhhhhhhhhhhhh\000RS1" \
+	" # aaaaaaaaaabbbbbbbbbb\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r\033[J# aaaaaaa >\r# "
+testseq "aaaaaaaaaabbbbbbbbbb\0033hhhhhhhhh\000RS11" \
+	" # aaaaaaaaaabbbbbbbbbb\b\b\b\b\b\b\b\b\b\b\r\033[J# aaabbbb +\b\b\b\b\b\b"
+testseq "aaaaaaaaaabbbbbbbbbb\0033\000RS12" \
+	" # aaaaaaaaaabbbbbbbbbb\b\r\033[J# bbbb    <\b\b\b\b\b\b"
+
+# ASCII resize: cols=13, cursor -1/0/+1
+testseq "aaaaaaaaaabbbbbbbbbb\0033hhhhhhhhhhhhh\000RS13" \
+	" # aaaaaaaaaabbbbbbbbbb\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r\033[J # aaaaaaa >\b\b\b"
+testseq "aaaaaaaaaabbbbbbbbbb\0033hhhhhhhhhhhh\000RS13" \
+	" # aaaaaaaaaabbbbbbbbbb\b\b\b\b\b\b\b\b\b\b\b\b\b\r\033[J # aaaaaab +\b\b\b\b\b\b"
+testseq "aaaaaaaaaabbbbbbbbbb\0033hhhhhhhhhhh\000RS13" \
+	" # aaaaaaaaaabbbbbbbbbb\b\b\b\b\b\b\b\b\b\b\b\b\r\033[J # aaaaabb +\b\b\b\b\b\b"
+
+# ASCII resize: cols=79/80/81
+testseq "aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccc\0033hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh\000RS79" \
+	" # aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccc\r # bbbbbbbbbbbbbcccccccccccccccccccccccc                                      <\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\bc\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\baaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccccc >\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r\033[J # aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccc >\r # "
+testseq "aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccc\0033hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh\000RS80" \
+	" # aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccc\r # bbbbbbbbbbbbbcccccccccccccccccccccccc                                      <\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\bc\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\baaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccccc >\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"
+testseq "aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccc\0033hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh\000RS81" \
+	" # aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccc\r # bbbbbbbbbbbbbcccccccccccccccccccccccc                                      <\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\bc\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\baaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccccc >\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r\033[J # aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccc >\r # "
-- 
2.52.0