* fix(cursor): keep the visual selection whole over characters outside the BMP The inclusive Nvim end column was turned into an exclusive VSCode one by adding a single UTF-16 unit. A character outside the BMP is two of them, so the selection ended in the middle of a surrogate pair and drifted away from where Nvim had the cursor: moving right over it took two presses and the selected region no longer matched. Step over the whole character instead, using its byte length. Closes #2535 * test(cursor): add visual selection tests for non-BMP characters * test(cursor): expect the astral pair to be whole in the mixed-character selection --------- Co-authored-by: wongxy <xiyao.wong@foxmail.com>
341 lines
9.6 KiB
TypeScript
341 lines
9.6 KiB
TypeScript
import { NeovimClient } from "neovim";
|
||
import vscode from "vscode";
|
||
|
||
import {
|
||
attachTestNvimClient,
|
||
sendVSCodeKeys,
|
||
assertContent,
|
||
closeAllActiveEditors,
|
||
closeNvimClient,
|
||
setCursor,
|
||
sendEscapeKey,
|
||
openTextDocument,
|
||
sendInsertKey,
|
||
} from "./integrationUtils";
|
||
|
||
describe("Multi-width characters", () => {
|
||
let client: NeovimClient;
|
||
before(async () => {
|
||
client = await attachTestNvimClient();
|
||
});
|
||
after(async () => {
|
||
await closeNvimClient(client);
|
||
await closeAllActiveEditors();
|
||
});
|
||
|
||
it("Works - 2col width chars", async () => {
|
||
await openTextDocument({ content: ["测试微服务", "", "没办法跳转到最后一个"].join("\n") });
|
||
|
||
await assertContent(
|
||
{
|
||
content: ["测试微服务", "", "没办法跳转到最后一个"],
|
||
cursor: [0, 0],
|
||
},
|
||
client,
|
||
);
|
||
|
||
await sendVSCodeKeys("ll");
|
||
await assertContent(
|
||
{
|
||
vsCodeCursor: [0, 2],
|
||
},
|
||
client,
|
||
);
|
||
|
||
await sendVSCodeKeys("x", 500);
|
||
await assertContent(
|
||
{
|
||
content: ["测试服务", "", "没办法跳转到最后一个"],
|
||
vsCodeCursor: [0, 2],
|
||
},
|
||
client,
|
||
);
|
||
|
||
// await sendVSCodeKeys("vll");
|
||
// await assertContent(
|
||
// {
|
||
// vsCodeSelections: [new vscode.Selection(0, 2, 0, 4)],
|
||
// },
|
||
// client,
|
||
// );
|
||
|
||
await setCursor(2, 5);
|
||
await assertContent(
|
||
{
|
||
vsCodeCursor: [2, 5],
|
||
},
|
||
client,
|
||
);
|
||
});
|
||
|
||
it("Works - 1col-2byte width chars", async () => {
|
||
await openTextDocument({ content: ["żżżżżżżż',", "ńńńńńńńń',"].join("\n") });
|
||
|
||
await assertContent(
|
||
{
|
||
content: ["żżżżżżżż',", "ńńńńńńńń',"],
|
||
cursor: [0, 0],
|
||
},
|
||
client,
|
||
);
|
||
await sendVSCodeKeys("lll");
|
||
await assertContent({ vsCodeCursor: [0, 3] }, client);
|
||
|
||
await sendVSCodeKeys("x");
|
||
await assertContent(
|
||
{
|
||
content: ["żżżżżżż',", "ńńńńńńńń',"],
|
||
vsCodeCursor: [0, 3],
|
||
},
|
||
client,
|
||
);
|
||
});
|
||
|
||
it("Works - 1col-3byte width chars", async () => {
|
||
await openTextDocument({ content: ["1ᵩᵩ123"].join("\n") });
|
||
|
||
await assertContent(
|
||
{
|
||
content: ["1ᵩᵩ123"],
|
||
cursor: [0, 0],
|
||
},
|
||
client,
|
||
);
|
||
await sendVSCodeKeys("ll");
|
||
await assertContent({ vsCodeCursor: [0, 2] }, client);
|
||
await sendVSCodeKeys("ll");
|
||
await assertContent({ vsCodeCursor: [0, 4] }, client);
|
||
|
||
await sendVSCodeKeys("x");
|
||
await assertContent(
|
||
{
|
||
content: ["1ᵩᵩ13"],
|
||
vsCodeCursor: [0, 4],
|
||
},
|
||
client,
|
||
);
|
||
});
|
||
|
||
it("Cursor is ok after exiting insert mode - 2 col chars", async () => {
|
||
await openTextDocument({ content: ["测试微服务", "", "没办法跳转到最后一个"].join("\n") });
|
||
|
||
await sendVSCodeKeys("lll");
|
||
await assertContent(
|
||
{
|
||
vsCodeCursor: [0, 3],
|
||
},
|
||
client,
|
||
);
|
||
await sendInsertKey();
|
||
|
||
await sendEscapeKey();
|
||
await assertContent(
|
||
{
|
||
vsCodeCursor: [0, 2],
|
||
},
|
||
client,
|
||
);
|
||
});
|
||
|
||
it("Cursor is ok after exiting insert mode - 1col-2byte chars", async () => {
|
||
await openTextDocument({ content: ["żżżżżżżż',", "ńńńńńńńń',"].join("\n") });
|
||
|
||
await sendVSCodeKeys("lll");
|
||
await assertContent(
|
||
{
|
||
vsCodeCursor: [0, 3],
|
||
},
|
||
client,
|
||
);
|
||
await sendInsertKey();
|
||
|
||
await sendEscapeKey();
|
||
await assertContent(
|
||
{
|
||
vsCodeCursor: [0, 2],
|
||
},
|
||
client,
|
||
);
|
||
});
|
||
|
||
it("Cursor is ok after exiting insert mode - 1col-3byte width chars", async () => {
|
||
await openTextDocument({ content: ["1ᵩᵩ123"].join("\n") });
|
||
|
||
await assertContent(
|
||
{
|
||
content: ["1ᵩᵩ123"],
|
||
cursor: [0, 0],
|
||
},
|
||
client,
|
||
);
|
||
await sendVSCodeKeys("ll");
|
||
await assertContent({ vsCodeCursor: [0, 2] }, client);
|
||
await sendInsertKey("a");
|
||
|
||
await sendEscapeKey();
|
||
await assertContent({ vsCodeCursor: [0, 2] }, client);
|
||
|
||
await sendVSCodeKeys("ll");
|
||
await sendInsertKey("a");
|
||
await sendEscapeKey();
|
||
await assertContent({ vsCodeCursor: [0, 4] }, client);
|
||
});
|
||
|
||
it("Cursor is ok after exiting insert mode at end of the line - 1col-3byte width chars", async () => {
|
||
await openTextDocument({ content: "ᵩ123" });
|
||
|
||
await sendInsertKey("A");
|
||
await sendEscapeKey();
|
||
await assertContent({ vsCodeCursor: [0, 3] }, client);
|
||
});
|
||
|
||
it("Multi byte with tabs", async () => {
|
||
await openTextDocument({ content: "\t\t测试\t微服务" });
|
||
|
||
await sendVSCodeKeys("gg0");
|
||
await sendVSCodeKeys("l");
|
||
await assertContent(
|
||
{
|
||
vsCodeCursor: [0, 1],
|
||
},
|
||
client,
|
||
);
|
||
|
||
await sendVSCodeKeys("l");
|
||
await assertContent(
|
||
{
|
||
vsCodeCursor: [0, 2],
|
||
},
|
||
client,
|
||
);
|
||
|
||
await sendVSCodeKeys("x");
|
||
await assertContent(
|
||
{
|
||
content: ["\t\t试\t微服务"],
|
||
vsCodeCursor: [0, 2],
|
||
},
|
||
client,
|
||
);
|
||
|
||
await sendVSCodeKeys("ll");
|
||
await assertContent(
|
||
{
|
||
vsCodeCursor: [0, 4],
|
||
},
|
||
client,
|
||
);
|
||
|
||
await sendVSCodeKeys("x");
|
||
await assertContent(
|
||
{
|
||
content: ["\t\t试\t服务"],
|
||
vsCodeCursor: [0, 4],
|
||
},
|
||
client,
|
||
);
|
||
});
|
||
|
||
it("Issue #503", async () => {
|
||
await openTextDocument({ content: "ŷaŷbŷcŷd = functionŷ(par1)" });
|
||
|
||
await sendVSCodeKeys("f(l");
|
||
await assertContent(
|
||
{
|
||
vsCodeCursor: [0, 26],
|
||
},
|
||
client,
|
||
);
|
||
|
||
await sendVSCodeKeys("ci(");
|
||
await assertContent({ vsCodeCursor: [0, 26], content: ["ŷaŷbŷcŷd = functionŷ()"] }, client);
|
||
});
|
||
|
||
it("Works - Emoji chars", async () => {
|
||
await openTextDocument({ content: ["🚀🕵️💡🤣", "", "🕵️🕵️🕵️🕵️"].join("\n") });
|
||
|
||
await assertContent(
|
||
{
|
||
content: ["🚀🕵️💡🤣", "", "🕵️🕵️🕵️🕵️"],
|
||
cursor: [0, 0],
|
||
},
|
||
client,
|
||
);
|
||
|
||
await sendVSCodeKeys("ll");
|
||
await assertContent(
|
||
{
|
||
vsCodeCursor: [0, 5],
|
||
},
|
||
client,
|
||
);
|
||
|
||
await sendVSCodeKeys("x");
|
||
await assertContent(
|
||
{
|
||
content: ["🚀🕵️🤣", "", "🕵️🕵️🕵️🕵️"],
|
||
vsCodeCursor: [0, 5],
|
||
},
|
||
client,
|
||
);
|
||
await sendVSCodeKeys("jjdw");
|
||
await assertContent(
|
||
{
|
||
content: ["🚀🕵️🤣", "", "🕵️🕵️"],
|
||
vsCodeCursor: [2, 3],
|
||
},
|
||
client,
|
||
);
|
||
});
|
||
|
||
it("Works - visual mode over chars outside the BMP", async () => {
|
||
// 𝕜 is U+1D55C: one Nvim character, but two UTF-16 units for VSCode
|
||
await openTextDocument({ content: ["a𝕜b"].join("\n") });
|
||
|
||
await assertContent({ content: ["a𝕜b"], cursor: [0, 0] }, client);
|
||
|
||
await sendVSCodeKeys("v");
|
||
await assertContent({ vsCodeSelections: [new vscode.Selection(0, 0, 0, 1)] }, client);
|
||
|
||
await sendVSCodeKeys("l");
|
||
await assertContent({ vsCodeSelections: [new vscode.Selection(0, 0, 0, 3)] }, client);
|
||
|
||
await sendVSCodeKeys("l");
|
||
await assertContent({ vsCodeSelections: [new vscode.Selection(0, 0, 0, 4)] }, client);
|
||
|
||
await sendEscapeKey();
|
||
});
|
||
|
||
it("Works - visual mode over consecutive non-BMP chars", async () => {
|
||
await openTextDocument({ content: ["a𝕜𝕝b"].join("\n") });
|
||
|
||
await sendVSCodeKeys("v");
|
||
await assertContent({ vsCodeSelections: [new vscode.Selection(0, 0, 0, 1)] }, client);
|
||
|
||
await sendVSCodeKeys("ll");
|
||
await assertContent({ vsCodeSelections: [new vscode.Selection(0, 0, 0, 5)] }, client);
|
||
|
||
await sendVSCodeKeys("l");
|
||
await assertContent({ vsCodeSelections: [new vscode.Selection(0, 0, 0, 6)] }, client);
|
||
|
||
await sendEscapeKey();
|
||
});
|
||
|
||
it("Works - visual mode across mixed BMP and non-BMP chars", async () => {
|
||
await openTextDocument({ content: ["aᵩ𝕜b"].join("\n") });
|
||
|
||
await sendVSCodeKeys("v");
|
||
await assertContent({ vsCodeSelections: [new vscode.Selection(0, 0, 0, 1)] }, client);
|
||
|
||
// the cursor sits on the astral char, whose two UTF-16 units both belong to
|
||
// the selection: a, the modifier letter and the pair take offsets 0 to 4
|
||
await sendVSCodeKeys("ll");
|
||
await assertContent({ vsCodeSelections: [new vscode.Selection(0, 0, 0, 4)] }, client);
|
||
|
||
await sendVSCodeKeys("l");
|
||
await assertContent({ vsCodeSelections: [new vscode.Selection(0, 0, 0, 5)] }, client);
|
||
|
||
await sendEscapeKey();
|
||
});
|
||
});
|