Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions lua/composer/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -770,13 +770,24 @@ end
-- `key:Name` label; field-level modifier encodings stay parked with the
-- input-pointer contract owner, so a flat label never matches a modified
-- role (the explicit session commands stay the exact path). Returns the
-- chord, the string "literal" for produced text (the caller inserts it), or
-- nil for unmapped labels.
-- chord, the string "literal" for produced text (the caller inserts it,
-- except CR/LF below), or nil for unmapped labels.
local function flat_key_shape(text)
if type(text) ~= "string" or text == "" then
return nil
end
if string.sub(text, 1, 4) ~= "key:" then
-- Headed Return arrives here, not as a label: both host capture entries
-- forward the produced `key.text` verbatim, and Return produces "\r"
-- (xkb/winit convention, the same byte the VT encoder emits for Enter).
-- A key-event CR/LF can only come from the Enter key itself (typing and
-- paste arrive as text/paste events, never key literals), so route it
-- through the bare Enter role for parity with the mock-chord and label
-- paths; otherwise custom enter-submits can never fire headed
-- (composer#21) and defaults silently bank a CR instead of a newline.
if text == "\r" or text == "\n" then
return { key = "enter", ctrl = false, alt = false, shift = false, super = false }
end
return "literal"
end
local name = string.lower(string.sub(text, 5))
Expand Down
35 changes: 35 additions & 0 deletions tests/scene-pump.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,41 @@ describe("production flat poll shape feeds the buffer", () => {
expect(run.host.submittedFrames).toEqual([]);
});

test("flat CR/LF literals route through the Enter role (composer#21)", async () => {
const run = await scenePump({ environment: { VISUAL: "nvim" } });
dispatch(run, OPEN_COMMAND);
injectFlat(run, { type: "key", text: "x" });
await pump(run);
expect(await bufferText(run)).toBe("x");
// Headed Return arrives as produced text "\r", not as a key:Enter
// label: under defaults it must newline exactly like the label path.
injectFlat(run, { type: "key", text: "\r" });
await pump(run);
expect(await bufferText(run)).toBe("x\n");
injectFlat(run, { type: "key", text: "\n" });
await pump(run);
expect(await bufferText(run)).toBe("x\n\n");
expect(await isOpen(run)).toBe(true);
expect(run.host.submittedFrames).toEqual([]);
});

test("flat CR literal submits under custom enter-submits (composer#21)", async () => {
const run = await scenePump({
environment: { VISUAL: "nvim" },
settings: { submit: "enter", newline: "shift+enter" },
});
dispatch(run, OPEN_COMMAND);
injectFlat(run, { type: "text", text: "CUSTOMSUB42" });
await pump(run);
injectFlat(run, { type: "key", text: "\r" });
await pump(run);
expect(await lastCode(run)).toBe("SUBMITTED");
expect(await isOpen(run)).toBe(false);
expect(await bufferText(run)).toBe("");
expect(run.host.submittedFrames).toHaveLength(1);
expect(run.host.submittedFrames[0]).toEqual(expectedFrame("CUSTOMSUB42"));
});

test("flat key literals insert; key:Enter newlines; other labels swallow", async () => {
const run = await scenePump({ environment: { VISUAL: "nvim" } });
dispatch(run, OPEN_COMMAND);
Expand Down
Loading