Skip to content

Panic when using fuzzy completion in a folder containing files starting with a multibyte unicode char #919

@dburburan

Description

@dburburan

Describe the bug

When fuzzy matching is on, and you have files starting with multibyte chars, and you type some letters and hit tab, you can trigger a panic.

This is related to a number of existing issues but I think I found a distinct case.
nushell/nushell#15302
nushell/nushell#13951
nushell/nushell#12680
nushell/nushell#11916

The bug is in the reedline crate, which I had a look at, but I'm putting the issue here as I think this where most of the issues are logged.

There were a couple of recent PRs that fixed some of these unicode issues -
#903
#886

But they don't fix this one, it still panics.

How to reproduce

You need fuzzy completions enabled in config.nu:

$env.config.completions.algorithm = "fuzzy"

And then do this

mkdir testdir
cd testdir
touch "abcdef"
touch "验abcdef"
ls ac<tab>

You get the following, or maybe it just drops out of completion, sometimes the panic message is suppressed.

x Main thread panicked.
  |-> at ~\reedline\src\menu\columnar_menu.rs:318:46
  `-> byte index 2 is not a char boundary; it is inside '验' (bytes 0..3) of `验abcdef`

Note that ab<tab> will not crash, only ac<tab>
This is because ab is a substring of the filename, but ac is not, and only produces matches in fuzzy mode.

It happens because in reedline/src/menu/columnar_menu.rs:311 (fn create_string) it tries to search for an exact substring to underline, but in this case it finds nothing, so match_position defaults to 0.

            let match_position = suggestion
                .value
                .to_lowercase()
                .find(&shortest_base.to_lowercase())
                .unwrap_or(0);

When the next line tries to slice the match result, it's assuming that we found the substring. If we did, then match_len would be the correct byte length and the slice would work properly.

In this case though it tries to slice 2 bytes from position 0 (we typed ac so that's length 2) but the first character of our file is which is actually 3 bytes long, so the slice is mid-char and panics.

            let match_str = &suggestion.value[match_position
                ..match_position + match_len.min(suggestion.value.len() - match_position)];

Expected behavior

Don't panic

Configuration

I built a recent version to check it was still happening

key value
version 0.105.1
major 0
minor 105
patch 1
branch main
commit_hash 1fe62ee6132372453d6bfeb95978ee61a2b77f3b
build_os windows-x86_64
build_target x86_64-pc-windows-msvc
rust_version rustc 1.85.1 (4eb161250 2025-03-15)
rust_channel 1.85.1-x86_64-pc-windows-msvc
cargo_version cargo 1.85.1 (d73d2caf9 2024-12-31)
build_time 2025-06-11 06:54:27 +10:00
build_rust_channel debug
allocator standard
features default, sqlite, trash
installed_plugins

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-CompletionsArea: Tab completion and inline hint completionsP-mediumMedium Priority: Bug affects quality of life, feature would improve experience for allbugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions