Skip to content

helptoc: FuzzySearch() highlights matched characters at wrong column for multibyte text #21056

Description

@VimWei

Steps to reproduce

  1. Create a markdown file with a multibyte heading, e.g. a single line # 中文标题
  2. :set filetype=markdown
  3. :colorscheme default — the fuzzy-match highlight uses the IncSearch group; some colorschemes make it indistinguishable from the surrounding text, so a default colorscheme makes the effect visible
  4. :packadd helptoc | HelpToc
  5. Press / and type 标题

Actual behaviour

The highlight lands on the wrong column — it overlaps a neighboring character and splits a multibyte character (only the first byte of the 3-byte UTF-8 char is highlighted).

Expected behaviour

Each matched character 标 and 题 is highlighted (via the help-fuzzy-toc text property, IncSearch).

Cause

matchfuzzypos() returns match positions as character indices (:h matchfuzzypos(): "list of character positions ... You can use byteidx() to convert a character position to a byte position"). But text property col/length are in bytes (:h prop_add(), :h popup-props: "counted in bytes").

In runtime/pack/dist/opt/helptoc/autoload/helptoc.vim, FuzzySearch():

props: pos[i]->copy()->map((_, col: number) => ({
    col: col + 1,
    length: 1,
    type: 'help-fuzzy-toc',
}))

col + 1 treats a character index as a byte column, and length: 1 is one byte instead of the byte length of the matched character. This is only correct for pure-ASCII text (where character index equals byte index), which is why it goes unnoticed in English help files.

Suggested fix

Convert character indices to byte offsets with byteidx() and use each matched character's byte length:

props: pos[i]->copy()->map((_, cp: number) => {
    const col = byteidx(match.text, cp) + 1
    const len = byteidx(match.text, cp + 1) - byteidx(match.text, cp)
    return {col: col, length: len, type: 'help-fuzzy-toc'}
})

Version of Vim

9.2.0907

Environment

  • Windows 10
  • gvim 9.2.0907 (also present in 9.1)

Logs and stack traces

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions