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:
Sat, 23 May 2026 01:14:20 +0200

Download raw body.

Thread
On Sun, 25 Jan 2026 17:23:12 +0100,
Kirill A. Korinsky <kirill@korins.ky> wrote:
> 
> 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.
> 

Anyone?

diff --git bin/ksh/edit.c bin/ksh/edit.c
index a510b5f9915..09254c1e8c4 100644
--- bin/ksh/edit.c
+++ bin/ksh/edit.c
@@ -26,7 +26,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 ***, int);
 static char	*x_tilde_expand(const char *);
@@ -64,7 +65,7 @@ x_sigwinch(int sig)
 	got_sigwinch = 1;
 }
 
-static void
+void
 check_sigwinch(void)
 {
 	if (got_sigwinch) {
@@ -81,11 +82,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);
@@ -126,12 +132,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 e1d200b9c93..e2978f3c7de 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 399ef59ce83..e3664df112f 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;
@@ -2166,4 +2174,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 e386fa1e65f..9ac55404ade 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)
@@ -1425,8 +1431,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;
@@ -1437,6 +1441,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) {
@@ -1453,9 +1468,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;
 }
 
 /*
@@ -2305,4 +2317,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 d9ae7967908..eef5886f999 100644
--- regress/bin/ksh/edit/vi.sh
+++ regress/bin/ksh/edit/vi.sh
@@ -233,3 +233,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 # "


-- 
wbr, Kirill