This is a bug I reported last year. The diff at bottom is based on this one from Lucas Gabriel Vuotto:
https://marc.info/?l=openbsd-tech&m=176952763532425&w=2 I found just one problem in Lucas' diff. Once applied, if in the example below you put the cursor in the first 'a' and press 'E' the cursor jups all the way to the last 'f'. $ ab def This is because both consecutive loops use ++ncursor in their conditions, then you get an unintentional double-skip. I added the following to vi.sh to show the error: testseq "ab def\00330ED" " # ab def\b\r # ab \b\b\b\b\b" $ sh vi.sh [...] >>> # ab def\010\015 # ab de \010\010<<< 00000000 20 23 20 61 62 20 64 65 66 08 0d 20 23 20 61 62 | # ab def.. # ab| 00000010 20 64 65 20 08 08 | de ..| 00000016 Description of the bug: When the cursor positions or lands on a UTF-8 character, the "e" command in ksh's vi mode gets stuck on that character and won't advance no matter how many times you press the "e" key. This is because the endword() function (file vi.c) doesn't recognize and skip UTF-8 continuation characters. Updated diff: Index: vi.c =================================================================== RCS file: /cvs/src/bin/ksh/vi.c,v diff -u -p -u -p -r1.70 vi.c --- vi.c 22 May 2026 18:11:08 -0000 1.70 +++ vi.c 26 Jun 2026 07:54:51 -0000 @@ -1591,15 +1591,18 @@ backword(int argcnt) static int endword(int argcnt) { - int ncursor, skip_space, want_letnum; + int ncursor, skip_space, skip_utf8_cont, want_letnum; unsigned char uc; ncursor = es->cursor; while (ncursor < es->linelen && argcnt--) { - skip_space = 1; + skip_space = skip_utf8_cont = 1; want_letnum = -1; while (++ncursor < es->linelen) { uc = es->cbuf[ncursor]; + if (skip_utf8_cont && isu8cont(uc)) + continue; + skip_utf8_cont = 0; if (isspace(uc)) { if (skip_space) continue; @@ -1664,8 +1667,11 @@ Endword(int argcnt) ncursor = es->cursor; while (ncursor < es->linelen && argcnt--) { while (++ncursor < es->linelen && - isspace((unsigned char)es->cbuf[ncursor])) + isu8cont((unsigned char)es->cbuf[ncursor])) ; + while (ncursor < es->linelen && + isspace((unsigned char)es->cbuf[ncursor])) + ncursor++; while (ncursor < es->linelen && !isspace((unsigned char)es->cbuf[ncursor])) ncursor++; Index: regress/bin/ksh/edit/vi.sh =================================================================== RCS file: /cvs/src/regress/bin/ksh/edit/vi.sh,v diff -u -p -u -p -r1.14 vi.sh --- regress/bin/ksh/edit/vi.sh 4 Apr 2026 09:33:18 -0000 1.14 +++ regress/bin/ksh/edit/vi.sh 25 Jun 2026 14:38:19 -0000 @@ -93,6 +93,16 @@ testseq "1.00 two\00330ED" " # 1.00 two\ # e: Move to end of word. testseq "onex two\00330eD" " # onex two\b\r # one \b\b\b\b\b\b" +# No infinite loop moving to end of {,big} word for non-ASCII UTF-8-ending +# words. +# EURO SIGN U+20AC is encoded as bytes 0xe2 0x82 0xac = \0342\0202\0254 +euro='\0342\0202\0254' +testseq "1.00$euro 2.00 three\00330EED" \ + " # 1.00$euro 2.00 three\b\r # 1.00$euro 2.0 \b\b\b\b\b\b\b\b" +testseq "one$euro twox three\00330eeD" \ + " # one$euro twox three\b\r # one$euro two \b\b\b\b\b\b\b\b" +testseq "ab def\00330ED" " # ab def\b\r # ab \b\b\b\b\b" + # F: Find character backward. # ;: Repeat last search. # ,: Repeat last search in opposite direction. -- Walter
