VulCode
Blog

vscode-neovim: zaznaczenie w trybie visual nie przecina już emoji na pół

Zaznaczenie kończące się na emoji albo znaku 𝕜 urywało się w połowie pary surogatów UTF-16. Koniec zaznaczenia przeskakuje teraz cały znak.

DualFroz - VulCode CEODualFroz - VulCode CEO1 min czytania
vscode-neovim: zaznaczenie w trybie visual nie przecina już emoji na pół - DualFroz - VulCode CEO
Tytuł zmiany w repozytorium
Keep visual selections whole over characters outside the BMP
Scalono
23.09.2026
Opisano tutaj
6.10.2026

Wpis powstał 13 dni po scaleniu zmiany.

vscode-neovim
Technologie:LuaTypeScriptUnicode

Co nie działało

Rozszerzenie vscode-neovim uruchamia prawdziwego Neovima pod spodem VS Code. Neovim liczy kolumny w bajtach UTF-8, a VS Code w jednostkach UTF-16, więc każde zaznaczenie trzeba przeliczyć. Znaki spoza podstawowej płaszczyzny Unicode, takie jak większość emoji albo 𝕜 (U+1D55C), zajmują w UTF-16 dwie jednostki, tak zwaną parę surogatów.

W trybie visual koniec zaznaczenia jest włączny, a w VS Code wyłączny, więc kod dodawał do końcowej kolumny jedną jednostkę UTF-16. Przy znaku z dwóch jednostek zaznaczenie kończyło się w połowie pary (zgłoszenie #2535).

Co zmieniliśmy

Zamiast dodawać jedną jednostkę UTF-16, kod najpierw przesuwa koniec zaznaczenia o cały znak w bajtach, a dopiero potem przelicza kolumnę na UTF-16:

lua
if vim.o.selection ~= "exclusive" then
  -- step over the whole character: adding one UTF-16 unit splits a surrogate pair
  end_col = byte_after_char(end_text, end_col)
end
local end_char = M.utf16_col(end_text, end_col)

Funkcja byte_after_char korzysta z vim.str_utf_end, żeby znaleźć ostatni bajt znaku, i pilnuje końca linii.

Jak to sprawdziliśmy

Doszły trzy testy integracyjne: zaznaczenie nad jednym znakiem spoza BMP, nad dwoma takimi znakami pod rząd i nad mieszanką zwykłych znaków wielobajtowych z tymi spoza BMP. Pakietu integracyjnego nie dało się uruchomić lokalnie, co napisaliśmy w opisie, więc pierwszym przebiegiem było CI projektu.

Efekt

Zmianę scalono 23 września 2026. Zaznaczenia w tekstach z emoji i znakami matematycznymi obejmują teraz całe znaki.

Tryb Vima w VS Code, w którym edycję obsługuje prawdziwy Neovim.

Język
TypeScript
Gwiazdki na GitHubie
7753

Ten sam przypadek przed zmianą i po niej

Na przykładzie z wpisu: co wychodziło przed poprawką i co wychodzi teraz.

Zaznaczenie w trybie visual kończy się na znaku
𝕜  U+1D55C

Neovim liczy ten znak jako 4 bajty UTF-8 (F0 9D 95 9C), a VS Code jako 2 jednostki UTF-16 (D835 DD5C).

Przed zmianą
D835

Do końca zaznaczenia dochodziła jedna jednostka UTF-16, więc kończyło się ono w połowie pary surogatów (zgłoszenie #2535).

Po zmianie
D835 DD5C

Koniec zaznaczenia przeskakuje cały znak w bajtach, a dopiero potem jest liczony w UTF-16, więc obejmuje obie jednostki.

Czego nas to nauczyło

JavaScript liczy długość tekstu w jednostkach UTF-16, a emoji zajmuje dwie. Każde przesunięcie kursora trzeba liczyć w znakach, nie w jednostkach.

Znak i opis z wpisu; bajty i jednostki policzone przy budowie strony. Testy integracyjne pull requesta #2683 obejmują jeden taki znak, dwa pod rząd i mieszankę ze zwykłymi znakami wielobajtowymi.

Pytania o zmianę #2683

Gdzie jest zmiana, jak ją sprawdziliśmy i kto ją przyjął.