From: Kirill A. Korinsky Subject: Re: bin/ksh: fix vi mode UTF-8 window display To: tech@openbsd.org Date: Sun, 25 Jan 2026 17:27:29 +0100 On Thu, 22 Jan 2026 17:02:59 +0100, Kirill A. Korinsky wrote: > > tech@ > > here the last my patch for ksh which address issues with UTF-8 disply logic > which was discovered by testing of resize terminal. > > It also included a fix for a crush reported by Walter Alejandro Iglesias. > > The vi line editor sizes its window buffers by column count and stops > rendering at the right edge. With UTF-8 input, a continuation byte can > land at the edge, producing garbage like "\\xb9<" and misplacing the > indicator; small-window resizes can also overrun the window buffer. > > Size the window buffers for UTF-8 and allow trailing continuation bytes > at the right edge. When a trailing UTF-8 byte is present, force the > indicator to print as a space plus the marker in the usual position. > Here an updated version which also included tests. Ok? diff --git bin/ksh/vi.c bin/ksh/vi.c index 168a9a8ffa5..1121fd43b40 100644 --- bin/ksh/vi.c +++ bin/ksh/vi.c @@ -24,6 +24,7 @@ #undef CTRL #define CTRL(x) ((x) & 0x1F) /* ASCII */ +#define UTF8_MAXBYTES 4 struct edstate { char *cbuf; /* main buffer to build the command line */ @@ -1370,7 +1371,7 @@ static int prompt_trunc; /* how much of prompt to truncate */ static int prompt_skip; /* how much of prompt to skip */ static int winwidth; /* available column positions */ static char *wbuf[2]; /* current & previous window buffer */ -static int wbuf_len; /* length of window buffers (x_cols-3)*/ +static int wbuf_len; /* length of window buffers */ static int win; /* number of window buffer in use */ static char morec; /* more character at right of window */ static char holdbuf[LINE]; /* place to hold last edit buffer */ @@ -1450,6 +1451,7 @@ static void calc_winsize(void) { const char *p; + int newlen; cur_col = pwidth = promptlen(prompt, &p); prompt_skip = p - prompt; @@ -1459,8 +1461,9 @@ calc_winsize(void) pwidth -= prompt_trunc; } else prompt_trunc = 0; - if (!wbuf_len || wbuf_len != x_cols - 3) { - wbuf_len = x_cols - 3; + newlen = (x_cols - 3) * UTF8_MAXBYTES; + if (!wbuf_len || wbuf_len != newlen) { + wbuf_len = newlen; wbuf[0] = aresize(wbuf[0], wbuf_len, APERM); wbuf[1] = aresize(wbuf[1], wbuf_len, APERM); } @@ -1881,6 +1884,7 @@ display(char *wb1, char *wb2, int leftside) int ncol; /* display column of the cursor */ int cnt; /* remaining display columns to fill */ int moreright; + int trailu8; char mc; /* new "more character" at the right of window */ unsigned char ch; @@ -1892,8 +1896,11 @@ display(char *wb1, char *wb2, int leftside) ncol = col = 0; cur = es->winleft; moreright = 0; + trailu8 = 0; twb1 = wb1; - while (col < winwidth && cur < es->linelen) { + while (cur < es->linelen && + (col < winwidth || + (col == winwidth && isu8cont(es->cbuf[cur])))) { if (cur == es->cursor && leftside) ncol = col + pwidth; if ((ch = es->cbuf[cur]) == '\t') { @@ -1909,7 +1916,8 @@ display(char *wb1, char *wb2, int leftside) } ch &= 0x7f; } - if (col < winwidth) { + if (col < winwidth || + (col == winwidth && isu8cont(ch))) { if (ch < ' ' || ch == 0x7f) { *twb1++ = '^'; if (++col < winwidth) { @@ -1917,6 +1925,8 @@ display(char *wb1, char *wb2, int leftside) col++; } } else { + if (col == winwidth && isu8cont(ch)) + trailu8 = 1; *twb1++ = ch; if (col == 0 || !isu8cont(ch)) col++; @@ -2009,8 +2019,10 @@ display(char *wb1, char *wb2, int leftside) mc = '>'; else mc = ' '; - if (mc != morec) { - ed_mov_opt(pwidth + winwidth + 1, wb1); + if (mc != morec || trailu8) { + ed_mov_opt(pwidth + winwidth, wb1); + x_putc(' '); + cur_col++; x_putc(mc); cur_col++; morec = mc; diff --git regress/bin/ksh/edit/vi.sh regress/bin/ksh/edit/vi.sh index b32c9f7a566..187417c7fe4 100644 --- regress/bin/ksh/edit/vi.sh +++ regress/bin/ksh/edit/vi.sh @@ -225,3 +225,35 @@ testseq "aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccc " # 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 # " + +# ASCII window indicator at left edge +testseq "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbb" \ + " # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbb\r # aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbb <\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" + +# UTF-8 window indicator at left edge +testseq "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0303\0266bbbbbbbbb" \ + " # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0303\0266bbbbbbbb\r # aaaaaaaaaaaaaaaaaaaaaaaaaaa\0303\0266bbbbbbbbb <\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" + +# ASCII window indicator at right edge +testseq "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb\00330" \ + " # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b <\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\bbb\b\baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa >\r # " + +# UTF-8 window indicator at right edge +testseq "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0303\0266b\00330" \ + " # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b <\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\0303\0266b\b\baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa >\r # " + +# ASCII window indicator on both sides +testseq "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\003340h" \ + " # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b <\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\baaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r # aaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb \b\b\b\b\b\b\b\b\b\b\b\b\b\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 # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb +\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" + +# UTF-8 window indicator on both sides +testseq "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0303\0266bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\003340h" \ + " # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b <\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\baaaaaa\0303\0266bbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r # aaaaaa\0303\0266bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb \b\b\b\b\b\b\b\b\b\b\b\b\b\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 # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0303\0266bbbbbbbbbbbbbbbbbbbbbbbbbb +\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" + +# ASCII redraw at right edge +testseq "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb\00330\0022" \ + " # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b <\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\bbb\b\baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa >\r # \r\r\n # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa >\r # " + +# UTF-8 redraw at right edge +testseq "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0303\0266b\00330\0022" \ + " # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b <\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\0303\0266b\b\baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa >\r # \r\r\n # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa >\r # " -- 2.52.0