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

Reply via email to