fix(vim): only shadow leader keys, not operators a mapping starts with (#2360)

Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-11-09 20:50:59 +08:00
1 джерело 44d871caee
коміт 7c80b68787
2 files changed
+23 -6

No files matched your search

+15
Переглянути файл
@@ -173,6 +173,21 @@ describe("Vim engine", () => {
d();
});
it.each([
["nmap dx <Action>(GotoFile)", ["d", "w"], "two\nthree"],
["nmap cs <Action>(GotoFile)", ["c", "w"], " two\nthree"],
["nnoremap yz yy", ["y", "y", "p"], "one two\none two\nthree"],
])("keeps built-in operators a mapping starts with (%s)", async (rc, keys, doc) => {
const view = makeView();
const cm = getCM(view)!;
applyRcCommands(parseVimrc(rc).commands);
for (const k of keys) Vim.handleKey(cm as never, k, "user");
await tick();
expect(view.state.doc.toString()).toBe(doc);
applyRcCommands([]);
view.destroy();
});
it("restores the built-in key the leader shadowed once the rc stops using it (#2337)", async () => {
const view = makeView();
const cm = getCM(view)!;
+8 -6
Переглянути файл
@@ -327,10 +327,10 @@ const LEADER_BUILTIN_DEFAULTS: Record<string, Record<string, unknown>> = {
};
/** Built-ins unmapped to make room for a user mapping's first key, by key. */
const shadowedBuiltins = new Map<string, Record<string, unknown> | null>();
const shadowedBuiltins = new Map<string, Record<string, unknown>>();
function firstKey(lhs: string): string {
return /^<[CSMA]-*\w+>|^./i.exec(lhs)?.[0] ?? lhs;
return /^<[^<>]+>|^./.exec(lhs)?.[0] ?? lhs;
}
/** Applies rc commands (replacing earlier rc mappings). Returns problems to report. */
@@ -342,17 +342,19 @@ export function applyRcCommands(commands: readonly RcCommand[]): string[] {
// one's first keys collide with. Restoring first keeps mapclear's
// user/default boundary honest — it assumes the default region never shrank.
for (const [key, entry] of shadowedBuiltins) {
if (entry) Vim._mapCommand(entry as never);
Vim._mapCommand(entry as never);
shadowedBuiltins.delete(key);
}
for (const c of commands) {
if (c.kind !== "map" && c.kind !== "mapEx") continue;
const key = firstKey(c.lhs);
// Only the single-key motions a leader reuses: unmapping an operator or prefix
// (`d`, `c`, `y`, `<C-d>`…) would break every command built on it.
const builtin = LEADER_BUILTIN_DEFAULTS[key];
if (!builtin || shadowedBuiltins.has(key)) continue;
// The builtins this shadows are context-less; `undefined` matches them.
if (!shadowedBuiltins.has(key) && Vim.unmap(key, undefined as unknown as string)) {
shadowedBuiltins.set(key, LEADER_BUILTIN_DEFAULTS[key] ?? null);
}
if (Vim.unmap(key, undefined as unknown as string)) shadowedBuiltins.set(key, builtin);
}
for (const c of commands) {
try {