From: jerry Subject: vi(1) bug autoindent with expandtab To: tech@openbsd.org Date: Mon, 15 Sep 2025 23:31:21 +0200 Hello, while using vi(1) I found a bug that I managed to fix. The bug happens also in nvi(1) from ports. Right now autoindent behaves differently when expandtab is also set. Steps to replicate the bug: * open vi with just autoindent * go into an empty line, go into insert mode and type * esc, move around and go back to the line * notice that tab stays there Repeating the same process but with also expandtab set, will leave the line empty, without the 2/4/8 spaces (the expanded tab). With expandtab, behaves in the same way as . Repeating the process above with with or without expandtab set will result in the line being cleared. The bug happens because with expandtab set calls the same function (txt_dent) that is called for , probably because the expandtab feature was added afterwards, and this difference was not noticed. I have fixed the function so that when called for it does not reset the autoindent characters. Bye, jerry diff --git vi/v_txt.c vi/v_txt.c index 5f245a0d457..202b4852ed2 100644 --- vi/v_txt.c +++ vi/v_txt.c @@ -29,10 +29,12 @@ #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) +enum dent_type {D_INDENT, D_OUTDENT, D_TAB}; + static int txt_abbrev(SCR *, TEXT *, CHAR_T *, int, int *, int *); static void txt_ai_resolve(SCR *, TEXT *, int *); static TEXT *txt_backup(SCR *, TEXTH *, TEXT *, u_int32_t *); -static int txt_dent(SCR *, TEXT *, int, int); +static int txt_dent(SCR *, TEXT *, int, enum dent_type); static int txt_emark(SCR *, TEXT *, size_t); static void txt_err(SCR *, TEXTH *); static int txt_fc(SCR *, TEXT *, int *); @@ -968,7 +970,7 @@ leftmargin: tp->lb[tp->cno - 1] = ' '; if (tp->ai == 0 || tp->cno > tp->ai + tp->offset) goto ins_ch; - (void)txt_dent(sp, tp, O_SHIFTWIDTH, 0); + (void)txt_dent(sp, tp, O_SHIFTWIDTH, D_OUTDENT); break; default: abort(); @@ -1184,7 +1186,7 @@ leftmargin: tp->lb[tp->cno - 1] = ' '; case K_CNTRLT: /* Add autoindent characters. */ if (!LF_ISSET(TXT_CNTRLT)) goto ins_ch; - if (txt_dent(sp, tp, O_SHIFTWIDTH, 1)) + if (txt_dent(sp, tp, O_SHIFTWIDTH, D_INDENT)) goto err; goto ebuf_chk; case K_RIGHTBRACE: @@ -1216,7 +1218,7 @@ leftmargin: tp->lb[tp->cno - 1] = ' '; case K_TAB: if (sp->showmode != SM_COMMAND && quote != Q_VTHIS && O_ISSET(sp, O_EXPANDTAB)) { - if (txt_dent(sp, tp, O_TABSTOP, 1)) + if (txt_dent(sp, tp, O_TABSTOP, D_TAB)) goto err; goto ebuf_chk; } @@ -1854,13 +1856,13 @@ txt_backup(SCR *sp, TEXTH *tiqh, TEXT *tp, u_int32_t *flagsp) * there are screens with different character representations. * * txt_dent -- - * Handle ^T indents, ^D outdents. + * Handle ^T indents, ^D outdents and when expandtab is set. * * If anything changes here, check the ex version to see if it needs similar * changes. */ static int -txt_dent(SCR *sp, TEXT *tp, int swopt, int isindent) +txt_dent(SCR *sp, TEXT *tp, int swopt, enum dent_type dent_type) { CHAR_T ch; u_long sw, ts; @@ -1891,7 +1893,7 @@ txt_dent(SCR *sp, TEXT *tp, int swopt, int isindent) COL_OFF(current, ts) : KEY_LEN(sp, tp->lb[cno]); target = current; - if (isindent) + if (dent_type == D_INDENT || dent_type == D_TAB) target += COL_OFF(target, sw); else { --target; @@ -1904,9 +1906,12 @@ txt_dent(SCR *sp, TEXT *tp, int swopt, int isindent) * and the indent flag because there's no single test. (^T can only * be detected by the cursor position, and while we know that the test * is always true for ^D, the cursor can be in more than one place, as - * "0^D" and "^D" are different.) + * "0^D" and "^D" are different). Since without expandtab is a + * normal character and does not reset the ai count, we do not reset it + * also when expandtab is set. */ - ai_reset = !isindent || tp->cno == tp->ai + tp->offset; + ai_reset = dent_type == D_OUTDENT || + (dent_type == D_INDENT && tp->cno == tp->ai + tp->offset); /* * Back up over any previous characters, changing them into @@ -1937,10 +1942,9 @@ txt_dent(SCR *sp, TEXT *tp, int swopt, int isindent) else { cno = current; tabs = 0; - if (!O_ISSET(sp, O_EXPANDTAB)) { + if (!O_ISSET(sp, O_EXPANDTAB)) for (; cno + COL_OFF(cno, ts) <= target; ++tabs) cno += COL_OFF(cno, ts); - } spaces = target - cno; }