Skip to content

Commit

Permalink
feat(prompt): add cbreak option (#106)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcushultman committed Jan 7, 2021
1 parent ed588e2 commit a637b54
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 7 deletions.
22 changes: 22 additions & 0 deletions examples/prompt/os_signals.ts
@@ -0,0 +1,22 @@
#!/usr/bin/env -S deno run --unstable

import { tty } from "../../ansi/tty.ts";
import { Toggle } from "../../prompt/toggle.ts";

const sig = Deno.signals.interrupt();
(async () => {
for await (const _ of sig) {
tty.cursorShow();
console.log("\nSigint received. Exiting deno process!");
Deno.exit(1);
}
})();

const confirmed: boolean = await Toggle.prompt({
message: "Please confirm",
cbreak: true,
});

console.log({ confirmed });

sig.dispose();
46 changes: 43 additions & 3 deletions prompt/README.md
Expand Up @@ -40,6 +40,7 @@
- [Prompt List](#prompt-list)
- [Dynamic Prompts](#dynamic-prompts)
- [Custom Prompts](#custom-prompts)
- [OS Signals](#os-signals)
- [API](#-api)
- [Types](#-types)
- [Contributing](#-contributing)
Expand Down Expand Up @@ -245,6 +246,43 @@ console.log(result);
$ deno run --unstable https://deno.land/x/cliffy/examples/prompt/custom_prompts.ts
```

### OS Signals

> The [cbreak](https://doc.deno.land/builtin/unstable#Deno.setRaw) option is an unstable feature and requires Deno => 1.6 and works currently only on Linux and macOS!
By default, cliffy will call `Deno.exit(0)` after the user presses `ctrl+c`. If you need to use a custom signal
handler, you can enable the `cbreak` option on your prompt. This will enable pass-through of os signals to deno,
allowing you to register your own signal handler. Currently, when using prompts like `Select` or `Toggle` and `cbreak`
mode is enabled, you must manually show the cursor before calling `Deno.exit()`. Maybe this will be improved somehow
in the future.

```typescript
import { tty } from "https://deno.land/x/cliffy/ansi/tty.ts";
import { Toggle } from "https://deno.land/x/cliffy/prompt/toggle.ts";

const sig = Deno.signals.interrupt();
(async () => {
for await (const _ of sig) {
tty.cursorShow();
console.log("\nSigint received. Exiting deno process!");
Deno.exit(1);
}
})();

const confirmed: boolean = await Toggle.prompt({
message: "Please confirm",
cbreak: true,
});

console.log({ confirmed });

sig.dispose();
```

```
$ deno run --unstable https://deno.land/x/cliffy/examples/prompt/os_signals.ts
```

## ❯ API

### prompt(prompts, options)
Expand All @@ -257,15 +295,16 @@ An prompt object has following options and all type specific options. See the li
| ----- | :---: | :---: | ----------- |
| name | `string` | Yes | The response will be saved under this key/property in the returned response object. |
| type | `string` | Yes | Defines the type of prompt to display. See the list of [prompt types](#-types) for valid values. |
| cbreak | `boolean` | No | cbreak mode enables pass-through of os signals to deno, allowing you to register your own signal handler (see [OS Signals](#os-singals)). **This is an unstable feature and requires Deno => 1.6!** |
| before | `(result, next) => Promise<void>` | No | `next()`execute's the next prompt in the list (for the before callback it's the current prompt). To change the index to a specific prompt you can pass the name or index of the prompt to the `next()` method. To skip this prompt you can pass `true` to the `next()` method. If `next()` isn't called all other prompts will be skipped. |
| after | `(result, next) => Promise<void>` | No | Same as `before` but will be executed *after* the prompt. |

The prompt method has also following global options.

| Param | Type | Required | Description |
| ----- | :---: | :---: | ----------- |
| before | `(result, next) => Promise<void>` | No | Same as above but will be executed before every prompt. |
| after | `(result, next) => Promise<void>` | No | Same as above but will be executed after every prompt. |
| before | `(result, next) => Promise<void>` | No | Same as above but will be executed before each prompt. |
| after | `(result, next) => Promise<void>` | No | Same as above but will be executed after each prompt. |

### Prompt.prompt(options)

Expand All @@ -292,9 +331,10 @@ All prompts have the following base options:
| default | `T` | No | Default value. Type depends on prompt type. |
| transform | `(value: V) => T \| undefined` | No | Receive user input. The returned value will be returned by the `.prompt()` method. |
| validate | `(value: T \| undefined) => ValidateResult` | No | Receive sanitized user input. Should return `true` if the value is valid, and an error message `String` otherwise. If `false` is returned, a default error message is shown |
| hint | `string` | No | Hint to display to the user. (not implemented) |
| hint | `string` | No | Hint to display to the user. |
| pointer | `string` | No | Change the pointer icon. |
| indent | `string` | No | Prompt indentation. Defaults to `' '` |
| cbreak | `boolean` | No | cbreak mode enables pass-through of os signals to deno, allowing you to register your own signal handler (see [OS Signals](#os-singals)). **This is an unstable feature and requires Deno => 1.6!** |

***

Expand Down
1 change: 0 additions & 1 deletion prompt/_generic_input.ts
Expand Up @@ -89,7 +89,6 @@ export abstract class GenericInput<
*/
protected async handleEvent(event: KeyEvent): Promise<void> {
switch (true) {
// @TODO: implement cbreak option
case event.name === "c" && event.ctrl:
this.tty.cursorShow();
Deno.exit(0);
Expand Down
15 changes: 13 additions & 2 deletions prompt/_generic_prompt.ts
Expand Up @@ -23,6 +23,7 @@ export interface GenericPromptOptions<T, V> {
pointer?: string;
indent?: string;
keys?: GenericPromptKeys;
cbreak?: boolean;
}

/** Generic prompt settings. */
Expand Down Expand Up @@ -225,7 +226,6 @@ export abstract class GenericPrompt<
*/
protected async handleEvent(event: KeyEvent): Promise<void> {
switch (true) {
// @TODO: implement cbreak option
case event.name === "c" && event.ctrl:
this.tty.cursorShow();
Deno.exit(0);
Expand Down Expand Up @@ -270,7 +270,12 @@ export abstract class GenericPrompt<
#readChar = async (): Promise<Uint8Array> => {
const buffer = new Uint8Array(8);

Deno.setRaw(Deno.stdin.rid, true);
// cbreak is only supported on deno >= 1.6.0, suppress ts-error.
(Deno.setRaw as setRaw)(
Deno.stdin.rid,
true,
{ cbreak: !!this.settings.cbreak },
);
const nread: number | null = await Deno.stdin.read(buffer);
Deno.setRaw(Deno.stdin.rid, false);

Expand Down Expand Up @@ -347,3 +352,9 @@ export abstract class GenericPrompt<
);
}
}

type setRaw = (
rid: number,
mode: boolean,
options?: { cbreak?: boolean },
) => void;
6 changes: 5 additions & 1 deletion prompt/prompt.ts
Expand Up @@ -45,6 +45,7 @@ type PromptResult<
};

interface PromptListOptions<R, N extends keyof R = keyof R> {
cbreak?: boolean;
before?: (
name: N,
opts: R,
Expand Down Expand Up @@ -2124,7 +2125,10 @@ class PromptList {
}

try {
this.result[this.prompt.name] = await prompt.prompt(this.prompt);
this.result[this.prompt.name] = await prompt.prompt({
cbreak: this.options?.cbreak,
...this.prompt,
});
} finally {
tty.cursorShow();
}
Expand Down

0 comments on commit a637b54

Please sign in to comment.