Severity: low · Category: robustness
Location: plugin/autocomplete.vim — s:MaybeTrigger(), lines 50-72
What's wrong
s:MaybeTrigger() derives the "last typed char" and the preceding chars with byte-offset string subscripts: l:col = col('.') (a byte column) and l:ch = l:line[l:col - 2], with further byte lookups at l:line[l:col - 3] and l:line[l:col - 4]. In Vimscript, str[n] indexes by byte, so any multibyte (UTF-8) character is split into its individual lead/trailing bytes. As a result, (1) when the just-typed character is multibyte, l:ch is a trailing byte such as / which does not match \k, so keyword completion never fires for accented-Latin / Cyrillic / CJK words; and (2) when a multibyte character merely precedes an ASCII word char, the l:line[l:col - 3] / l:line[l:col - 4] \k tests evaluate against half-character bytes, so even completing a plain ASCII char typed right after a multibyte letter is suppressed. The 'fire once per word' invariant and the omnifunc trigger detection therefore misbehave on any non-ASCII buffer content. Severity is low because iVim targets ASCII-centric code editing, but it is a genuine correctness gap for the documented multibyte edge case.
Evidence
Lines 50-55:
let l:col = col('.')
if l:col < 2
return
endif
let l:line = getline('.')
let l:ch = l:line[l:col - 2]
...
elseif l:ch =# '\k'
\ && l:col >= 3 && l:line[l:col - 3] =# '\k'
\ && (l:col < 4 || l:line[l:col - 4] !~# '\k')
Verified empirically (Vim 9.0, utf-8): for line='naïv', col=6 (cursor after typing 'v'), l:line[col-2]='v' matches \k but l:line[col-3]= (trailing byte of ï) does NOT match \k, so the keyword-fire expression evaluates to 0 — completion is silently suppressed. Likewise for line='aé', col=4, l:ch= does not match \k.
Suggested fix
Operate on characters, not bytes. Replace the byte subscripts with character-aware extraction, e.g. use strpart(l:line, byteidx-based start, len, 1) (the {chars} arg added in patch-8.2.x) or split the substring up to the cursor with split(strpart(l:line, 0, l:col-1), '\zs') and inspect the last few list elements. At minimum, match the trailing text with a regex anchored at the cursor (e.g. matchstr(strpart(l:line,0,l:col-1), '\k{1,}$')) so multibyte word characters are handled as whole characters.
Filed from an automated multi-agent source review (2026-05-29); finding adversarially verified at high confidence. Line numbers reflect the audit-fixes-2026-05 working tree.
Severity: low · Category: robustness
Location:
plugin/autocomplete.vim— s:MaybeTrigger(), lines 50-72What's wrong
s:MaybeTrigger() derives the "last typed char" and the preceding chars with byte-offset string subscripts: l:col = col('.') (a byte column) and l:ch = l:line[l:col - 2], with further byte lookups at l:line[l:col - 3] and l:line[l:col - 4]. In Vimscript, str[n] indexes by byte, so any multibyte (UTF-8) character is split into its individual lead/trailing bytes. As a result, (1) when the just-typed character is multibyte, l:ch is a trailing byte such as / which does not match \k, so keyword completion never fires for accented-Latin / Cyrillic / CJK words; and (2) when a multibyte character merely precedes an ASCII word char, the l:line[l:col - 3] / l:line[l:col - 4] \k tests evaluate against half-character bytes, so even completing a plain ASCII char typed right after a multibyte letter is suppressed. The 'fire once per word' invariant and the omnifunc trigger detection therefore misbehave on any non-ASCII buffer content. Severity is low because iVim targets ASCII-centric code editing, but it is a genuine correctness gap for the documented multibyte edge case.
Evidence
Lines 50-55:
let l:col = col('.')
if l:col < 2
return
endif
let l:line = getline('.')
let l:ch = l:line[l:col - 2]
...
elseif l:ch =
# '\k'# '\k'\ && l:col >= 3 && l:line[l:col - 3] =
\ && (l:col < 4 || l:line[l:col - 4] !~# '\k')
Verified empirically (Vim 9.0, utf-8): for line='naïv', col=6 (cursor after typing 'v'), l:line[col-2]='v' matches \k but l:line[col-3]= (trailing byte of ï) does NOT match \k, so the keyword-fire expression evaluates to 0 — completion is silently suppressed. Likewise for line='aé', col=4, l:ch= does not match \k.
Suggested fix
Operate on characters, not bytes. Replace the byte subscripts with character-aware extraction, e.g. use strpart(l:line, byteidx-based start, len, 1) (the {chars} arg added in patch-8.2.x) or split the substring up to the cursor with split(strpart(l:line, 0, l:col-1), '\zs') and inspect the last few list elements. At minimum, match the trailing text with a regex anchored at the cursor (e.g. matchstr(strpart(l:line,0,l:col-1), '\k{1,}$')) so multibyte word characters are handled as whole characters.
Filed from an automated multi-agent source review (2026-05-29); finding adversarially verified at high confidence. Line numbers reflect the
audit-fixes-2026-05working tree.