Skip to content

Commit

Permalink
fix(wrangler): keypress event name is optional (#7345)
Browse files Browse the repository at this point in the history
* fix(wrangler): keypress event name is optional

* Create poor-chefs-kneel.md

* add additional tests

---------

Co-authored-by: Somhairle MacLeòid <[email protected]>
  • Loading branch information
edmundhung and penalosa authored Dec 16, 2024
1 parent d4626f1 commit 15aa936
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 29 deletions.
5 changes: 5 additions & 0 deletions .changeset/poor-chefs-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": patch
---

fix(wrangler): keypress event name is optional
68 changes: 57 additions & 11 deletions packages/wrangler/src/__tests__/cli-hotkeys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@ import registerHotKeys from "../cli-hotkeys";
import { logger } from "../logger";
import { mockConsoleMethods } from "./helpers/mock-console";
import { useMockIsTTY } from "./helpers/mock-istty";
import type { KeypressEvent } from "../utils/onKeyPress";

const writeToMockedStdin = (input: string) =>
_internalKeyPressCallback({
name: input,
sequence: input,
ctrl: false,
meta: false,
shift: false,
});
let _internalKeyPressCallback: (input: KeypressEvent) => void;
import type { Key } from "node:readline";

const writeToMockedStdin = (input: string | Key) =>
_internalKeyPressCallback(
typeof input === "string"
? {
name: input,
sequence: input,
ctrl: false,
meta: false,
shift: false,
}
: input
);
let _internalKeyPressCallback: (input: Key) => void;
vitest.mock("../utils/onKeyPress", async () => {
return {
onKeyPress(callback: () => void) {
Expand Down Expand Up @@ -81,6 +85,48 @@ describe("Hot Keys", () => {
handlerA.mockClear();
});

it("handles meta keys", async () => {
const handlerCtrl = vi.fn();
const handlerMeta = vi.fn();
const handlerShift = vi.fn();
const options = [
{ keys: ["ctrl+a"], label: "ctrl option", handler: handlerCtrl },
{ keys: ["meta+a"], label: "meta option", handler: handlerMeta },
{ keys: ["shift+a"], label: "shift option", handler: handlerShift },
];

registerHotKeys(options);

writeToMockedStdin("a");
expect(handlerCtrl).not.toHaveBeenCalled();

writeToMockedStdin("ctrl+a");
expect(handlerCtrl).toHaveBeenCalled();
handlerCtrl.mockClear();

writeToMockedStdin("meta+a");
expect(handlerMeta).toHaveBeenCalled();
handlerMeta.mockClear();

writeToMockedStdin("shift+a");
expect(handlerShift).toHaveBeenCalled();
handlerShift.mockClear();
});

it("ignores missing key names", async () => {
const handlerA = vi.fn();
const options = [
{ keys: ["a"], label: "first option", handler: handlerA },
];

registerHotKeys(options);

writeToMockedStdin({
shift: false,
});
expect(handlerA).not.toHaveBeenCalled();
});

it("ignores unbound keys", async () => {
const handlerA = vi.fn();
const handlerD = vi.fn();
Expand Down
19 changes: 12 additions & 7 deletions packages/wrangler/src/cli-hotkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,22 @@ export default function (
}

const unregisterKeyPress = onKeyPress(async (key) => {
let char = key.name.toLowerCase();
const entries: string[] = [];

if (key?.meta) {
char = "meta+" + char;
if (key.name) {
entries.push(key.name.toLowerCase());
}
if (key?.ctrl) {
char = "ctrl+" + char;
if (key.meta) {
entries.unshift("meta");
}
if (key?.shift) {
char = "shift+" + char;
if (key.ctrl) {
entries.unshift("ctrl");
}
if (key.shift) {
entries.unshift("shift");
}

const char = entries.join("+");

for (const { keys, handler, disabled } of options) {
if (unwrapHook(disabled)) {
Expand Down
15 changes: 4 additions & 11 deletions packages/wrangler/src/utils/onKeyPress.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import readline from "readline";
import readline from "node:readline";
import { PassThrough } from "stream";
import isInteractive from "../is-interactive";
import type { Key } from "node:readline";

export type KeypressEvent = {
name: string;
sequence: string;
ctrl: boolean;
meta: boolean;
shift: boolean;
};

export function onKeyPress(callback: (key: KeypressEvent) => void) {
export function onKeyPress(callback: (key: Key) => void) {
// Listening for events on process.stdin (eg .on('keypress')) causes it to go into 'old mode'
// which keeps this nodejs process alive even after calling .off('keypress')
// WORKAROUND: piping stdin via a transform stream allows us to call stream.destroy()
Expand All @@ -24,7 +17,7 @@ export function onKeyPress(callback: (key: KeypressEvent) => void) {
process.stdin.setRawMode(true);
}

const handler = async (_char: string, key: KeypressEvent) => {
const handler = async (_char: string, key: Key) => {
if (key) {
callback(key);
}
Expand Down

0 comments on commit 15aa936

Please sign in to comment.